<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Sekyoro</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://www.sekyoro.top/</id>
  <link href="https://www.sekyoro.top/" rel="alternate"/>
  <link href="https://www.sekyoro.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Sekyoro</rights>
  <subtitle>什么也无法舍弃的人，什么也做不了.</subtitle>
  <title>Sekyoro的博客小屋</title>
  <updated>2026-06-28T16:03:43.147Z</updated>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="gradle" scheme="https://www.sekyoro.top/tags/gradle/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>使用Gradle构建项目,尝试用的属性,与数据库、缓存,MQ搭配,以及构建模板。<br><span id="more"></span></p><h1 id="Gradle介绍"><a href="#Gradle介绍" class="headerlink" title="Gradle介绍"></a>Gradle介绍</h1><p>Gradle 是一个构建自动化工具</p><p>用来做三件事：</p><ul><li>编译 Java / Kotlin</li><li>管理依赖（类似 Maven）</li><li>组织多模块工程</li></ul><p>一个 Gradle 项目本质是：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Gradle = 一堆 <span class="keyword">Project</span> + 每个 <span class="keyword">Project</span> 的 build.gradle</span><br></pre></td></tr></table></figure><p>每个 build.gradle 里都是“配置块”。</p><h2 id="Gradle核心模型"><a href="#Gradle核心模型" class="headerlink" title="Gradle核心模型"></a>Gradle核心模型</h2><p>Gradle 有 3 个核心对象：</p><p> <strong>Project（项目）</strong></p><p>每个模块都是一个 Project：</p><figure class="highlight cmake"><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></pre></td><td class="code"><pre><span class="line">root <span class="keyword">project</span></span><br><span class="line"> ├── api</span><br><span class="line"> ├── service</span><br><span class="line"> ├── repository</span><br></pre></td></tr></table></figure><p>每个 <code>build.gradle</code> = 一个 Project</p><p><strong>Task（任务）</strong></p><p>Gradle 的执行单位：</p><figure class="highlight subunit"><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></pre></td><td class="code"><pre><span class="line">compileJava</span><br><span class="line"><span class="keyword">test</span></span><br><span class="line"><span class="keyword"></span>bootJar</span><br><span class="line">build</span><br></pre></td></tr></table></figure><p><strong>Dependency（依赖）</strong></p><p>Java 代码依赖：</p><figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">implementation</span> <span class="string">&#x27;org.springframework.boot:spring-boot-starter&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><figure class="highlight markdown"><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"><span class="bullet">1.</span> 初始化（init）</span><br><span class="line"><span class="bullet">2.</span> 配置（configuration）</span><br><span class="line"><span class="bullet">3.</span> 执行（execution）</span><br></pre></td></tr></table></figure><p>关键点： <code>build.gradle</code> 在“配置阶段”全部执行</p><p>所以：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">println</span><span class="params">(<span class="string">&quot;hello&quot;</span>)</span></span></span><br></pre></td></tr></table></figure><p>会在执行 task 之前就打印。</p><p>一个项目结构让设计如下</p><figure class="highlight maxima"><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">purchase-<span class="built_in">system</span></span><br><span class="line">│</span><br><span class="line">├── common          （基础模型）</span><br><span class="line">├── <span class="built_in">contract</span>        （RPC接口）</span><br><span class="line">├── repository      （DAO）</span><br><span class="line">├── service         （业务 + Dubbo provider）</span><br><span class="line">└── api             （HTTP + Dubbo consumer）</span><br></pre></td></tr></table></figure><h2 id="插件块"><a href="#插件块" class="headerlink" title="插件块"></a>插件块</h2><p>给 <strong>build.gradle 这个脚本本身</strong>加依赖,通过buildscript或者plugins.给项目“增加能力”</p><p>比如：</p><ul><li>java → 可以编译 Java</li><li>spring boot → 可以打 bootJar</li></ul><p><code>plugins {}</code>：<strong>现代插件机制（声明式、Gradle 官方推荐）</strong><br> <code>buildscript {}</code>：<strong>旧插件机制（手动把依赖塞进构建脚本 classpath）</strong></p><figure class="highlight groovy"><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">buildscript &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">    dependencies &#123;</span><br><span class="line">        classpath <span class="string">&quot;org.springframework.boot:spring-boot-gradle-plugin:3.3.0&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过repositories中的仓库查找对应插件并放到<code>classpath</code>.然后才apply plugin使用插件. Gradle本身自带一些插件,比如<code>java</code>,可以直接apply</p><p>而`plugins自己去“插件仓库”下载 plugin</p><p>然后自动解析 plugin marker,找找 implementation jar,自动 apply. 配置下载仓库在settings.gradle中,默认使用Gradle官方的</p><figure class="highlight groovy"><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">plugins &#123;</span><br><span class="line">    id <span class="string">&quot;org.springframework.boot&quot;</span> version <span class="string">&quot;3.3.0&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="仓库块"><a href="#仓库块" class="headerlink" title="仓库块"></a>仓库块</h2><figure class="highlight fsharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">repositories</span> &#123;</span><br><span class="line">    mavenCentral()</span><br><span class="line">    <span class="keyword">maven</span> &#123; url <span class="string">&quot;https://maven.aliyun.com/repository/public&quot;</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>告诉 Gradle 去哪里下载依赖</p><h2 id="依赖块"><a href="#依赖块" class="headerlink" title="依赖块"></a>依赖块</h2><figure class="highlight groovy"><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">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 给 Java 代码引入库</p><div class="table-container"><table><thead><tr><th>关键字</th><th>作用</th></tr></thead><tbody><tr><td>implementation</td><td>运行 + 编译</td></tr><tr><td>compileOnly</td><td>只编译</td></tr><tr><td>testImplementation</td><td>测试用</td></tr><tr><td>runtimeOnly</td><td>运行时</td></tr></tbody></table></div><h2 id="ext"><a href="#ext" class="headerlink" title="ext"></a>ext</h2><figure class="highlight nginx"><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></pre></td><td class="code"><pre><span class="line"><span class="section">ext</span> &#123;</span><br><span class="line">    <span class="attribute">lombokVersion</span> = <span class="string">&#x27;1.18.34&#x27;</span></span><br><span class="line">    dubboVersion = <span class="string">&#x27;3.2.15&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>作用： 定义“全局变量</p><p>现在推荐替代方案</p><figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gradle.<span class="keyword">properties</span></span><br></pre></td></tr></table></figure><h2 id="allprojects"><a href="#allprojects" class="headerlink" title="allprojects"></a>allprojects</h2><figure class="highlight fsharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">allprojects</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>作用： 对“所有 project（包括 root + 子模块）”生效</p><h2 id="subprojects"><a href="#subprojects" class="headerlink" title="subprojects"></a>subprojects</h2><figure class="highlight gradle"><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="keyword">subprojects</span> &#123;</span><br><span class="line">    apply plugin: <span class="string">&#x27;java&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">dependencies</span> &#123;</span><br><span class="line">        testImplementation <span class="string">&#x27;org.junit.jupiter:junit-jupiter:5.10.2&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>作用： 只对“子模块”生效（不包括 root）</p><p>作用:</p><p>统一 Java plugin</p><p>统一 Lombok</p><p>统一 test 配置</p><p><strong>project(“:xxx”) </strong></p><figure class="highlight stylus"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">project</span><span class="params">(<span class="string">&quot;:api&quot;</span>)</span></span> &#123;</span><br><span class="line">    dependencies &#123;</span><br><span class="line">        implementation <span class="built_in">project</span>(<span class="string">&quot;:common&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置“某一个指定模块”</p><p><strong>dependencyManagement {}</strong></p><figure class="highlight fsharp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">dependencyManagement</span> &#123;</span><br><span class="line">    <span class="keyword">imports</span> &#123;</span><br><span class="line">        mavenBom <span class="string">&quot;org.springframework.boot:spring-boot-dependencies:3.3.0&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>作用：统一版本号（不在 dependencies 写版本）</p><p><strong>tasks （任务块）</strong></p><figure class="highlight isbl"><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></pre></td><td class="code"><pre><span class="line"><span class="variable"><span class="class">tasks</span>.register</span>(<span class="string">&#x27;hello&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable">doLast</span> &#123;</span><br><span class="line">        <span class="function"><span class="title">println</span>(<span class="string">&quot;hello gradle&quot;</span>)</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>作用：定义构建步骤</p><h2 id="常用插件"><a href="#常用插件" class="headerlink" title="常用插件"></a>常用插件</h2><p><strong>自带插件</strong></p><p>1）java（最重要）</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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;java&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>提供能力：</p><ul><li><p>compileJava</p></li><li><p>jar</p></li><li><p>test</p></li><li><p>sourceSets</p><p> Java 项目基础插件</p></li></ul><p>2）java-library（更专业）</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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;java-library&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>多了：</p><ul><li>api / implementation 区分</li><li><p>更适合模块化项目</p><p>推荐多模块项目用这个</p></li></ul><p><strong>maven-publish</strong></p><p>Gradle Maven Publish Plugin</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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;maven-publish&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 用于发布 jar 到 Maven 仓库</p><p>数据库 / ORM 常见插件</p><p><strong>MyBatis Generator</strong></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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;org.mybatis.generator&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 自动生成 mapper / entity</p><p><strong>Spring 生态插件</strong></p><p>Spring Boot Plugin</p><p>Spring Boot Gradle Plugin</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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;org.springframework.boot&#x27;</span> version <span class="string">&#x27;3.3.0&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>提供：</p><ul><li>bootJar（fat jar）</li><li>bootRun</li><li>自动依赖管理</li></ul><p><strong>Spring Dependency Management</strong></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></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    <span class="built_in">id</span> <span class="string">&#x27;io.spring.dependency-management&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>提供：BOM 管理（不写版本号）</p><h2 id="基于Spring-boot-starter与Mybatis整合"><a href="#基于Spring-boot-starter与Mybatis整合" class="headerlink" title="基于Spring-boot-starter与Mybatis整合"></a>基于Spring-boot-starter与Mybatis整合</h2><p><a href="https://mybatis.org/spring-boot-starter/zh/mybatis-spring-boot-autoconfigure/index.html">mybatis-spring-boot-autoconfigure – 简介</a></p><figure class="highlight groovy"><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">dependencies &#123;</span><br><span class="line">  implementation(<span class="string">&quot;org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>MyBatis-Spring-Boot-Starter 将会：</p><ul><li>自动探测存在的 <code>DataSource</code></li><li>将使用 <code>SqlSessionFactoryBean</code> 创建并注册一个 <code>SqlSessionFactory</code> 的实例，并将探测到的 <code>DataSource</code> 作为数据源</li><li>将创建并注册一个从 <code>SqlSessionFactory</code> 中得到的 <code>SqlSessionTemplate</code> 的实例</li><li>自动扫描你的 mapper，将它们与 <code>SqlSessionTemplate</code> 相关联，并将它们注册到Spring 的环境（context）中去，这样它们就可以被注入到你的 bean 中</li></ul><p><strong>数据源是基础底层，<code>SqlSessionFactory</code> 利用数据源创建连接并作为核心工厂，生产出具体的执行器 <code>SqlSessionTemplate</code>，而 <code>SqlSessionTemplate</code> 在幕后真正驱动并支撑 <code>Mapper</code> 接口的方法执行。</strong></p><p>它们之间的分层依赖与调用关系如下：</p><figure class="highlight scss"><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></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[DataSource (数据源)]</span></span><br><span class="line">       ▲</span><br><span class="line">       │ 提供数据库连接池 (如 HikariCP)</span><br><span class="line">       │</span><br><span class="line"><span class="selector-attr">[SqlSessionFactory (会话工厂)]</span></span><br><span class="line">       ▲</span><br><span class="line">       │ 作为单例核心引擎，用来创建会话</span><br><span class="line">       │</span><br><span class="line"><span class="selector-attr">[SqlSessionTemplate (会话模板)]</span></span><br><span class="line">       ▲</span><br><span class="line">       │ 线程安全，代理并管理实际的 SqlSession 会话与生命周期</span><br><span class="line">       │</span><br><span class="line"><span class="selector-attr">[Mapper 接口 (动态代理对象)]</span></span><br></pre></td></tr></table></figure><p><a href="https://mybatis.org/spring-boot-starter/zh/mybatis-spring-boot-autoconfigure/index.html">mybatis-spring-boot-autoconfigure – 简介</a></p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>引入依赖 </p><p>在 <code>pom.xml</code> 中引入 MyBatis 自己的 Starter（它会顺着依赖把需要的工厂类和模板类全部下载下来）：</p><figure class="highlight xml"><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.spring.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.0.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><ol><li>核心配置文件 (<code>application.yml</code>)</li></ol><p>只需要配置两部分：一是数据源（让它能自动探测到 <code>DataSource</code>），二是告诉它你的 SQL 映射文件（XML）放在哪。</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="comment"># 1. 对应第一句：“自动探测存在的 DataSource”</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/your_db?useUnicode=true&amp;characterEncoding=utf-8</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">your_password</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="comment"># 2. 对应第四句：告诉系统你的 XML 映射文件在哪里，好让它自动扫描并关联</span></span><br><span class="line">  <span class="attr">mapper-locations:</span> <span class="string">classpath:mapper/*.xml</span></span><br><span class="line">  <span class="attr">configuration:</span></span><br><span class="line">    <span class="attr">map-underscore-to-camel-case:</span> <span class="literal">true</span> <span class="comment"># 顺便开启驼峰命名转换（下划线转驼峰）</span></span><br></pre></td></tr></table></figure><ol><li>在代码中打个“招呼”</li></ol><p>最后在Spring Boot 启动类（或者配置类）上，加上一个 <strong><code>@MapperScan</code></strong> 注解。</p><p>这是为了告诉它：<strong>“我的 Mapper 接口都放在这个包底下了，你快去自动扫描它们，把它们和 <code>SqlSessionTemplate</code> 关联起来吧！”</strong></p><p>Java</p><figure class="highlight less"><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="variable">@SpringBootApplication</span></span><br><span class="line"><span class="variable">@MapperScan</span>(<span class="string">&quot;com.example.ecommerce.mapper&quot;</span>) <span class="comment">//  告诉系统扫描哪里的 Mapper 接口</span></span><br><span class="line">public class EcommerceApplication &#123;</span><br><span class="line">    <span class="selector-tag">public</span> <span class="selector-tag">static</span> <span class="selector-tag">void</span> <span class="selector-tag">main</span>(String[] args) &#123;</span><br><span class="line">        <span class="selector-tag">SpringApplication</span><span class="selector-class">.run</span>(EcommerceApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="整合springBoot与Redis"><a href="#整合springBoot与Redis" class="headerlink" title="整合springBoot与Redis"></a>整合springBoot与Redis</h2><p><a href="https://docs.springframework.org.cn/spring-kafka/reference/quick-tour.html">快速导览 :: Spring Kafka - Spring 框架</a></p><p>在 Spring Boot 中，<strong><code>spring-boot-starter-data-redis</code></strong>（通常简称为 spring-redis-starter）是整合 Redis 的核心组件。它不仅引入了 Redis 客户端驱动，还通过 Spring Boot 的 <strong>Auto-Configuration（自动装配）</strong> 机制，让开发者几乎可以零配置直接使用。</p><ol><li>默认客户端驱动：Lettuce</li></ol><ul><li>在老的 Spring Boot 1.x 版本中，默认使用的是 <strong>Jedis</strong> 客户端。</li><li>从 <strong>Spring Boot 2.x/3.x</strong> 开始，官方默认将驱动切换为了 <strong>Lettuce</strong>。</li><li><strong>为什么用 Lettuce？</strong> Jedis 的多线程并发是基于连接池的（线程不安全），而 Lettuce 是基于 <strong>Netty</strong> 的，它的连接可以在多个线程间<strong>共享</strong>，属于异步非阻塞的架构，在高并发场景下性能更优、资源消耗更小。</li></ul><ol><li>自动装配类：<code>RedisAutoConfiguration</code></li></ol><p>当你引入了 Starter 后，Spring Boot 的类路径扫描会激活 <code>RedisAutoConfiguration</code> 这个自动配置类。它在幕后默默做了两件事：</p><ul><li><strong>自动创建连接工厂（<code>RedisConnectionFactory</code>）</strong>：它会读取你在 <code>yml</code> 中配置的 host、port 等参数，实例化一个 <code>LettuceConnectionFactory</code>，负责建立与 Redis 服务器的物理连接。</li><li><strong>自动注入两个核心模板类</strong>：<ul><li><strong><code>RedisTemplate&lt;Object, Object&gt;</code></strong>：通用模板，键值对都可以是任意对象。</li><li><strong><code>StringRedisTemplate</code></strong>：专门处理 <code>String</code> 类型的模板（因为 Redis 中 90% 的场景都是字符串）。</li></ul></li></ul><ol><li>序列化机制（关键踩坑点）</li></ol><p>Redis 底层只存储字节数组（<code>byte[]</code>），而 Java 是一门面向对象的语言。因此，Java 对象存入 Redis 必须经过<strong>序列化</strong>。</p><ul><li><strong><code>StringRedisTemplate</code></strong>：默认使用 <code>StringRedisSerializer</code>。它存进去的就是人类直观可见的普通字符串。</li><li><strong><code>RedisTemplate</code></strong>：默认使用 <code>JdkSerializationSerializer</code>。这会导致两个严重问题：<ol><li>存入 Redis 的 Key 和 Value 前面会带有一串乱码般的十六进制前缀（如 <code>\xac\xed\x00\x05t\x00...</code>）。</li><li>难以通过可视化工具（如 RDM）直接查看，且跨语言（如 Python 读写）极不友好。</li></ol></li></ul><h3 id="配置-1"><a href="#配置-1" class="headerlink" title="配置"></a>配置</h3><p>引入依赖 (POM)</p><p>除了 Starter 本身，如果需要配置连接池，必须引入 <code>commons-pool2</code>。</p><figure class="highlight xml"><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="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 必须引入，否则 lettuce 连接池配置无法生效 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.commons<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-pool2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><ol><li>核心配置文件 (<code>application.yml</code>)</li></ol><p>标准 Lettuce 连接池配置：</p><figure class="highlight yaml"><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 class="attr">spring:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">redis:</span></span><br><span class="line">      <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">your_password</span></span><br><span class="line">      <span class="attr">database:</span> <span class="number">0</span>                <span class="comment"># 默认使用 0 号库</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">5000ms</span>            <span class="comment"># 连接超时时间</span></span><br><span class="line">      <span class="attr">connect-timeout:</span> <span class="string">5000ms</span>    <span class="comment"># 路由建立连接超时时间</span></span><br><span class="line">      <span class="attr">lettuce:</span></span><br><span class="line">        <span class="attr">pool:</span></span><br><span class="line">          <span class="attr">max-active:</span> <span class="number">32</span>         <span class="comment"># 连接池最大连接数（高并发下调大）</span></span><br><span class="line">          <span class="attr">max-idle:</span> <span class="number">16</span>           <span class="comment"># 连接池中的最大空闲连接</span></span><br><span class="line">          <span class="attr">min-idle:</span> <span class="number">4</span>            <span class="comment"># 连接池中的最小空闲连接</span></span><br><span class="line">          <span class="attr">max-wait:</span> <span class="string">2000ms</span>       <span class="comment"># 连接耗尽时，消费者最大等待时间</span></span><br></pre></td></tr></table></figure><ol><li>自定义配置类：重写 <code>RedisTemplate</code>（必做步骤）</li></ol><p>为了解决上面提到的 <strong>“JDK 序列化乱码”</strong> 问题，在实际开发中，我们<strong>必须</strong>自己写一个配置类，把默认的序列化方式替换为 <strong>JSON 序列化</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        template.setConnectionFactory(factory);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 使用 JSON 工具（这里以 GenericJackson2JsonRedisSerializer 为例）</span></span><br><span class="line">        <span class="type">GenericJackson2JsonRedisSerializer</span> <span class="variable">jsonSerializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 设置 Key 和 HashKey 采用 String 序列化（保证人类可读，无乱码）</span></span><br><span class="line">        template.setKeySerializer(RedisSerializer.string());</span><br><span class="line">        template.setHashKeySerializer(RedisSerializer.string());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 设置 Value 和 HashValue 采用 JSON 序列化</span></span><br><span class="line">        template.setValueSerializer(jsonSerializer);</span><br><span class="line">        template.setHashValueSerializer(jsonSerializer);</span><br><span class="line"></span><br><span class="line">        template.afterPropertiesSet();</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>三、 总结：如何选择 <code>RedisTemplate</code> 和 <code>StringRedisTemplate</code>？</p><p>在代码中注入并使用它们时，遵循以下基本原则：</p><ol><li><strong>如果你只存纯文本、数字、或者你自己手动转好的 JSON 字符串</strong>： 直接使用 <strong><code>StringRedisTemplate</code></strong>。它免去了任何框架层的序列化开销，性能最高，且在 Redis 内呈现最干净的字符串。</li><li><strong>如果你想直接把一个 Java 对象（如 <code>User</code> 实体、<code>Order</code> 实体）丢进 Redis</strong>： 使用你刚才自定义好序列化器的 <strong><code>RedisTemplate&lt;String, Object&gt;</code></strong>。它会在底层自动帮你把对象转成标准 JSON 串存入，取出来时自动反序列化回 Java 对象，开发体验最顺畅。</li></ol><h2 id="整合springBoot与Kafka"><a href="#整合springBoot与Kafka" class="headerlink" title="整合springBoot与Kafka"></a>整合springBoot与Kafka</h2><p>引入依赖</p><figure class="highlight xml"><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"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.kafka<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span>  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-kafka<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span>  <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>spring-kafka</code> 的自动装配核心是 <strong><code>KafkaAutoConfiguration</code></strong> 类，它在项目启动时主要帮你完成了以下几件大事：</p><ol><li>自动构建三大工厂（基础层）</li></ol><p>它会读取 <code>yml</code> 中的集群地址等参数，在后台自动实例化：</p><ul><li><strong><code>ProducerFactory</code></strong>：生产者工厂，负责专门创建、管理和复用低层的 Kafka 生产者实例（<code>KafkaProducer</code>）。</li><li><strong><code>ConsumerFactory</code></strong>：消费者工厂，负责配置消费组 ID、反序列化器，并创建原生的 <code>KafkaConsumer</code>。</li><li><strong><code>KafkaListenerContainerFactory</code></strong>：监听器容器工厂（<strong>最核心</strong>）。它负责把你的 <code>@KafkaListener</code> 注解转换为后台不断轮询（<code>poll()</code>）消息的异步线程池容器。</li></ul><ol><li>自动注入核心发送模板：<code>KafkaTemplate</code></li></ol><p>类似于 <code>RedisTemplate</code>，Spring 自动为你提供了一个 <code>KafkaTemplate&lt;K, V&gt;</code>。</p><ul><li>它对原生的生产者进行了高级封装。</li><li>它默认是<strong>线程安全</strong>的，你可以在任何 Service 里直接注入并调用 <code>.send(topic, message)</code>。</li><li>支持同步发送（等待 Broker 回应）和异步发送（通过 <code>CompletableFuture</code> 回调接收发送结果）。</li></ul><ol><li><code>@KafkaListener</code> 幕后的死循环轮询</li></ol><p>当你在某个方法上加上 <code>@KafkaListener(topics = &quot;my-topic&quot;)</code> 时：</p><ol><li>Spring 启动后，监听器容器工厂会为这个方法单独开启<strong>独立的线程</strong>。</li><li>线程内部会进入一个 <code>while(true)</code> 的<strong>死循环</strong>，源源不断地调用 Kafka 原生的 <code>consumer.poll(timeout)</code> 方法去拉取消息。</li><li>一旦拉取到消息，通过反射调用你写的方法，将消息作为参数传入。</li></ol><p><strong>引入依赖 (POM)</strong></p><figure class="highlight xml"><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.kafka<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-kafka<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><ol><li>核心配置文件 (<code>application.yml</code>)</li></ol><p>在实际的业务场景中，我们必须精细控制<strong>ACK应答级别</strong>以防丢消息，以及<strong>关闭自动提交</strong>改为手动提交。</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">kafka:</span></span><br><span class="line">    <span class="attr">bootstrap-servers:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span><span class="string">:9092</span>        <span class="comment"># Kafka 集群地址，多个用逗号隔开</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># ==================== 1. 生产者配置 ====================</span></span><br><span class="line">    <span class="attr">producer:</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">3</span>                             <span class="comment"># 消息发送失败后的重试次数</span></span><br><span class="line">      <span class="attr">acks:</span> <span class="string">all</span>                              <span class="comment"># 应答级别。all(或-1) 代表所有副本都同步完才算成功，最安全</span></span><br><span class="line">      <span class="attr">batch-size:</span> <span class="number">16384</span>                      <span class="comment"># 批量发送大小 (16KB)，提升吞吐量</span></span><br><span class="line">      <span class="attr">buffer-memory:</span> <span class="number">33554432</span>                <span class="comment"># 生产端本地缓冲区大小 (32MB)</span></span><br><span class="line">      <span class="attr">key-serializer:</span> <span class="string">org.apache.kafka.common.serialization.StringSerializer</span></span><br><span class="line">      <span class="attr">value-serializer:</span> <span class="string">org.apache.kafka.common.serialization.StringSerializer</span></span><br><span class="line">      </span><br><span class="line">    <span class="comment"># ==================== 2. 消费者配置 ====================</span></span><br><span class="line">    <span class="attr">consumer:</span></span><br><span class="line">      <span class="attr">group-id:</span> <span class="string">ecommerce-order-group</span>       <span class="comment"># 默认消费组 ID</span></span><br><span class="line">      <span class="attr">enable-auto-commit:</span> <span class="literal">false</span>              <span class="comment"># 关闭自动提交！推荐手动提交，防止业务代码报错时消息丢失</span></span><br><span class="line">      <span class="attr">auto-offset-reset:</span> <span class="string">earliest</span>            <span class="comment"># 当各分区没有初始偏移量时的策略：earliest/latest</span></span><br><span class="line">      <span class="attr">key-deserializer:</span> <span class="string">org.apache.kafka.common.serialization.StringDeserializer</span></span><br><span class="line">      <span class="attr">value-deserializer:</span> <span class="string">org.apache.kafka.common.serialization.StringDeserializer</span></span><br><span class="line">      <span class="attr">max-poll-records:</span> <span class="number">500</span>                  <span class="comment"># 单次 poll 轮询最大拉取条数</span></span><br><span class="line">      </span><br><span class="line">    <span class="comment"># ==================== 3. 监听Container配置 ====================</span></span><br><span class="line">    <span class="attr">listener:</span></span><br><span class="line">      <span class="comment"># 手动立即提交偏移量。配合 enable-auto-commit: false 使用。</span></span><br><span class="line">      <span class="comment"># 业务代码正常执行完后，Spring 会自动帮你调用 commitSync()</span></span><br><span class="line">      <span class="attr">ack-mode:</span> <span class="string">manual_immediate</span>             </span><br><span class="line">      <span class="attr">concurrency:</span> <span class="number">3</span>                         <span class="comment"># 消费者并发度。建议设置为和你 Topic 的 Partition（分区）数量一致，充分利用多线程并行消费</span></span><br></pre></td></tr></table></figure><p>三、 核心代码使用示例</p><ol><li>生产者：发送消息</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/order&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> KafkaTemplate&lt;String, String&gt; kafkaTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/create&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">orderJson</span> <span class="operator">=</span> <span class="string">&quot;&#123;\&quot;orderId\&quot;: 10023, \&quot;amount\&quot;: 299.00&#125;&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 异步发送消息</span></span><br><span class="line">        CompletableFuture&lt;SendResult&lt;String, String&gt;&gt; future = kafkaTemplate.send(<span class="string">&quot;order-topic&quot;</span>, orderJson);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 绑定发送结果回调（非阻塞）</span></span><br><span class="line">        future.whenComplete((result, ex) -&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> (ex == <span class="literal">null</span>) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;消息发送成功，偏移量为：&quot;</span> + result.getRecordMetadata().offset());</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                System.err.println(<span class="string">&quot;消息发送失败：&quot;</span> + ex.getMessage());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Order processing...&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>消费者：监听并手动确认（Ack）</li></ol><p>当你在上面配置了 <code>ack-mode: manual_immediate</code>，你的消费方法里可以引入 <code>Acknowledgment</code> 对象。当业务代码顺利跑完，调用 <code>ack.acknowledge()</code> 告诉 Kafka：“这条消息我吃下了，你可以推进 Offset 了”。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderConsumer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// concurrency = &quot;3&quot; 表示启动 3 个线程同时消费，对应 yml 中的全局配置，也可以在这里局部覆盖</span></span><br><span class="line">    <span class="meta">@KafkaListener(topics = &quot;order-topic&quot;, groupId = &quot;ecommerce-order-group&quot;, concurrency = &quot;3&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onMessage</span><span class="params">(ConsumerRecord&lt;String, String&gt; record, Acknowledgment ack)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1. 收到消息，打印日志</span></span><br><span class="line">            System.out.printf(<span class="string">&quot;线程 %s 收到消息 -&gt; 分区: %d, 消息体: %s%n&quot;</span>, </span><br><span class="line">                    Thread.currentThread().getName(), record.partition(), record.value());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 2. 执行你的电商核心业务逻辑（如：扣减库存、发送通知等）</span></span><br><span class="line">            <span class="comment">// doYourBusiness(record.value());</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 3. 业务成功，手动提交</span></span><br><span class="line">            ack.acknowledge(); </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 4. 业务异常，这里千万别 ack！可以流转到死信队列（DLQ）或者触发重试机制</span></span><br><span class="line">            System.err.println(<span class="string">&quot;消费异常，暂不提交偏移量: &quot;</span> + e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/06/24/Gradle%E5%85%A5%E9%97%A8%E4%B8%8E%E4%BD%BF%E7%94%A8/</id>
    <link href="https://www.sekyoro.top/2026/06/24/Gradle%E5%85%A5%E9%97%A8%E4%B8%8E%E4%BD%BF%E7%94%A8/"/>
    <published>2026-06-23T16:45:51.000Z</published>
    <summary>
      <![CDATA[<p>使用Gradle构建项目,尝试用的属性,与数据库、缓存,MQ搭配,以及构建模板。<br>]]>
    </summary>
    <title>Gradle入门与使用</title>
    <updated>2026-06-28T16:03:43.147Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>这周的两件大事包括K3模型发布以及pi-agent逐渐走入视野。这无疑又给Agent应用的热度推上新的高峰<br><span id="more"></span></p><h1 id="Agent发展"><a href="#Agent发展" class="headerlink" title="Agent发展"></a>Agent发展</h1><h3 id="Loop-Engineering"><a href="#Loop-Engineering" class="headerlink" title="Loop Engineering"></a>Loop Engineering</h3><p><a href="https://addyosmani.com/blog/loop-engineering/">AddyOsmani.com - Loop Engineering</a></p><p>过去两年，AI 开发的重点正在不断变化：从研究如何写好提示词（Prompt），逐步发展到组织上下文（Context）、编排工具与流程（Harness），再到构建能够自主运行、持续交付结果的循环系统（Loop）</p><p>人不再逐轮手敲 Prompt，而是写一个<strong>外循环系统</strong>，由系统定时/事件触发 → 派发 Agent → 验证产出 → 记状态 → 决定继续或停止。</p><figure class="highlight vhdl"><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">Prompt：怎么问 AI</span><br><span class="line">↓</span><br><span class="line"><span class="keyword">Context</span>：给 AI 什么信息</span><br><span class="line">↓</span><br><span class="line">Harness：如何组织 AI 的能力</span><br><span class="line">↓</span><br><span class="line"><span class="keyword">Loop</span>：如何让 AI 持续创造结果</span><br></pre></td></tr></table></figure><p>Loop Engineering 并非凭空而来，它是 AI 编程工具能力演进的自然结果。</p><div class="table-container"><table><thead><tr><th style="text-align:left">阶段</th><th style="text-align:left">代表工具</th><th style="text-align:left">工作方式</th><th style="text-align:left">瓶颈</th></tr></thead><tbody><tr><td style="text-align:left">第一代：自动补全</td><td style="text-align:left">GitHub Copilot 早期版本</td><td style="text-align:left">补全当前行或函数，人类主导所有决策</td><td style="text-align:left">只能辅助，不能自主</td></tr><tr><td style="text-align:left">第二代：对话式</td><td style="text-align:left">ChatGPT、Claude.ai</td><td style="text-align:left">问一句，答一句，人类手动推进每一步</td><td style="text-align:left">人类成为瓶颈，速度受限于打字速度</td></tr><tr><td style="text-align:left">第三代：Agent 自主循环</td><td style="text-align:left">Claude Code、OpenAI Codex Agent</td><td style="text-align:left">Agent 自主规划、执行、验证，循环迭代直到完成</td><td style="text-align:left">如何设计让 Loop 可靠运行的系统</td></tr></tbody></table></div><p>第三代工具的出现意味着，工程师的核心竞争力从会写提示词变成了会设计 Loop。</p><p>Loop Engineering 是设计、运营和持续改进反馈循环的工程实践，这些循环使 AI 编程 Agent 能够自主完成规划、执行代码修改、观察结果并在多轮迭代中完成任务。</p><p><strong>内循环 vs 外循环</strong></p><ul><li>内循环（Agent 自带）：感知 → 推理 → 行动 → 观察</li><li>外循环（你设计的）：触发 → 发现任务 → 分派 → 验证 → 持久化 → 下一轮 </li></ul><p><strong>完整 Loop 的六大组件</strong></p><ol><li><strong>Automations</strong>：触发器（cron、CI、Codex Automations UI、<code>/loop</code> 命令）——Loop 的心跳</li><li><strong>Worktrees</strong>：Git 工作树隔离，多 Agent 并行不撞文件</li><li><strong>Skills</strong>：<code>SKILL.md</code> 沉淀项目规范，避免每次冷启动重学</li><li><strong>Connectors / MCP</strong>：接 GitHub、Slack、CI 等真实工具链</li><li><strong>Sub-agents</strong>：Maker/Checker 分离，写代码的和验代码的分开</li><li><strong>Memory / State</strong>：跨轮持久化（STATE.md、issue、DB）</li></ol><p><strong>五阶段闭环</strong>：Discover → Plan → Act → Observe → Iterate；<strong>验证器（Verifier）最关键</strong>——不能让干活 Agent 自己宣布”完成”。</p><p><strong>与 Prompt Engineering 的关系</strong>：不是替代，是上一层。Loop 由多条 Prompt 组成，Prompt 烂 → Loop 加速产垃圾。</p><p><strong>成本与风险提醒</strong>：定时 Loop 每次触发烧 Token；并行瓶颈在人审 PR 的速度，不在能开几个 Worktree；自主程度分四级（只读→草稿→半自动→全自动）。</p><h2 id="模型评比"><a href="#模型评比" class="headerlink" title="模型评比"></a>模型评比</h2><p><a href="https://arena.ai/leaderboard/agent">Agent Arena | AI Agent Performance Leaderboard</a></p><p><img data-src="https://files.seeusercontent.com/2026/07/25/hBw9/image-20260725151630642.png" alt="image-20260725151630642"></p><p><a href="https://www.swebench.com/">swebench.com</a></p><p>代码编写模型排名</p><p>🥇<strong>DeepSeek V4 / V4 Pro</strong>：在 Agent 模式下解决真实 GitHub Bug 的通过率在开源界处于领跑地位，结合工具链调用能力强。</p><p><strong>🥈 Kimi K3 / K2 系列</strong>：依靠百万级超长上下文（1M Context Window），在多文件重构、代码库项目级解析上表现优异。</p><p><strong>🥉 Qwen3-Coder / MiniMax-M3</strong>：Qwen 依然是开源界使用最广泛、协议最友好的代码基座，单次 Patch 质量与通用语法生成非常稳健。</p><div class="table-container"><table><thead><tr><th><strong>梯队与模型</strong></th><th><strong>代表型号</strong></th><th><strong>协议许可</strong></th><th><strong>最佳应用场景</strong></th></tr></thead><tbody><tr><td><strong>第一梯队（顶级 Agent &amp; 复杂工程）</strong></td><td><code>DeepSeek-V4 Pro / R1</code> <code>Kimi K3 / K2.6</code></td><td>MIT / 开放许可</td><td><strong>复杂 Bug 修复、跨文件系统重构、算法难题推导</strong>。几乎可以替代大部分常规开发中的 API 编码工作。</td></tr><tr><td><strong>第二梯队（全能代码基座 &amp; 商业友好）</strong></td><td><code>Qwen3-Coder-480B</code> <code>GLM 5.2 / 4.6</code> <code>MiniMax-M3</code></td><td>Apache-2.0 / MIT</td><td><strong>企业私有化部署、API 自动化调用、Web 开发全栈</strong>。其中 Qwen 对商业化最为宽松友好。</td></tr><tr><td><strong>本地单机/边缘端推荐（显卡可跑）</strong></td><td><code>DeepSeek-R1-Distill-Qwen</code> <code>Qwen2.5-Coder-7B/14B/32B</code> <code>Gemma 3 27B</code></td><td>各种开源许可</td><td><strong>本地 IDE 插件（如 Continue/Roo Code）实时补全与私有代码提示</strong>。单张消费级显卡（如 RTX 4090）即可流畅运行。</td></tr></tbody></table></div><p>简单来说Kimi,DeepSeek,GLM,Qwen都是国产大模型的佼佼者也都有开源模型供搭建使用</p><h2 id="智能体经典泛式构建"><a href="#智能体经典泛式构建" class="headerlink" title="智能体经典泛式构建"></a>智能体经典泛式构建</h2><p>最具代表性的三种：</p><ul><li><strong>ReAct (Reasoning and Acting)：</strong> 一种将“思考”和“行动”紧密结合的范式，让智能体边想边做，动态调整。</li><li><strong>Plan-and-Solve：</strong> 一种“三思而后行”的范式，智能体首先生成一个完整的行动计划，然后严格执行。</li><li><strong>Reflection：</strong> 一种赋予智能体“反思”能力的范式，通过自我批判和修正来优化结果。</li></ul><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://pi.dev/">https://pi.dev/</a><br><a href="https://hello-agents.datawhale.cc/#/">https://hello-agents.datawhale.cc/#/</a></p><p><a href="https://addyosmani.com/blog/loop-engineering/">AddyOsmani.com - Loop Engineering</a></p><h1 id=""><a href="#" class="headerlink" title=" "></a> </h1><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/06/17/Agent%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%BC%80%E5%8F%91/</id>
    <link href="https://www.sekyoro.top/2026/06/17/Agent%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%BC%80%E5%8F%91/"/>
    <published>2026-06-17T10:03:28.000Z</published>
    <summary>
      <![CDATA[<p>这周的两件大事包括K3模型发布以及pi-agent逐渐走入视野。这无疑又给Agent应用的热度推上新的高峰<br>]]>
    </summary>
    <title>Agent从入门到开发</title>
    <updated>2026-07-28T16:26:42.068Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>起因是想用自动化的流程做一些游戏任务,右面发现了MAA相关资源并尝试学习开发一些流程。这里总结一下踩坑和经验。<br><span id="more"></span></p><h1 id="开发环境搭建"><a href="#开发环境搭建" class="headerlink" title="开发环境搭建"></a>开发环境搭建</h1><p>最早是从<a href="https://github.com/MaaAssistantArknights/MaaAssistantArknights">MaaAssistantArknights/MaaAssistantArknights</a>中脱离的框架,目前用于<a href="https://github.com/MAA1999/M9A">MAA1999/M9A: 重返未来</a>，<a href="https://github.com/overflow65537/MAA_SnowBreak">overflow65537/MAA_SnowBreak</a>等等项目。框架在下文中简称maa或maafw.</p><p>官方简介如下：</p><blockquote><p><strong>MaaFramework</strong> 是基于图像识别技术、运用 <a href="https://github.com/MaaAssistantArknights/MaaAssistantArknights">MAA</a> 开发经验去芜存菁、完全重写的新一代自动化黑盒测试框架。低代码的同时仍拥有高扩展性，旨在打造一款丰富、领先、且实用的开源库，助力开发者轻松编写出更好的黑盒测试程序，并推广普及。</p></blockquote><p>推荐vscode+<a href="https://github.com/neko-para/maa-support-extension">neko-para/maa-support-extension</a>进行开发,也方便使用插件测试。</p><p><strong>注意</strong>,插件有个<code>maa.agentTimeout</code>设置超时时间,如果你发现连接不上,并且一般都是30秒关闭连接,那就有可能是超时了,可以把时间设置更长一点。</p><p><img data-src="https://files.seeusercontent.com/2026/06/04/5xbR/image-20260604165943683.png" alt="image-20260604165943683"></p><h1 id="重点知识"><a href="#重点知识" class="headerlink" title="重点知识"></a>重点知识</h1><p>官方文档<a href="https://maafw.com/docs/1.1-QuickStarted">1.1 快速开始 | MaaFramework</a></p><p>一般采用JSON+自定义逻辑扩展。JSON中会通过识别+动作定义节点。常用的动作包括点击,按键等等。</p><h2 id="任务流水线协议"><a href="#任务流水线协议" class="headerlink" title="任务流水线协议"></a>任务流水线协议</h2><p>在<code>/assets/resource</code>下包含资源文件夹,例如<code>image</code>,<code>model</code>以及<code>pipeline</code>.</p><p><code>pipeline</code>中定义任务流水线。<a href="https://maafw.com/docs/3.1-PipelineProtocol#节点流程">3.1 任务流水线（Pipeline）协议 | MaaFramework</a></p><p>一个节点包含节点名称,以及<strong>识别</strong>和<strong>行为</strong>,<strong>后续</strong>,以及前后等待时间,识别等待时间和最多进入次数等。</p><blockquote><p>对当前节点的 <code>next</code> 列表进行顺序检测，依次尝试识别每个子节点</p><p>若本轮均未命中，则继续下一轮检测；直到超时或有节点命中</p><p>当检测到某个子节点匹配成功时，立即终止后续节点检测。执行匹配节点的 <code>action</code> 定义的操作</p></blockquote><p>从entry开始,针对第一个节点开始识别并进行动作,若该节点识别命中,并且动作成功,进入next列表。在next列表中循环检测,如果一个子节点识别命中则进入,如果动作成功进入其next列表重复流程,如果失败进入该节点on_error列表。</p><p>如果第一个节点识别命中但动作失败同样进入on_error列表,如果识别失败直接终止。</p><p>如果某个节点的next列表识别超时,则进入当前节点的on_error列表。如果全都走了一遍未命中且超时则终止。</p><p>设置<code>timeout</code>控制当前节点的 <code>next</code> 列表识别的超时时间</p><blockquote><p>当满足以下任意条件时，任务流程终止：</p><ul><li>当前节点的 next 列表为空（若存在 <code>[JumpBack]</code> 回跳点，会先回跳）</li><li>当前节点的 <code>next</code> 列表未命中且超时</li><li>外部调用 <code>post_stop</code> 或执行了 <code>StopTask</code> 动作</li></ul><p>核心:识别成功才算进入,如果一个节点下的next列表识别超时则走on_error(但next中节点还未识别完),如果都未命中且超时则退出</p></blockquote><p>识别recognition类型包括默认 <a href="https://maafw.com/docs/3.1-PipelineProtocol#directhit"><code>DirectHit</code></a> 。<br>可选的值：<a href="https://maafw.com/docs/3.1-PipelineProtocol#directhit"><code>DirectHit</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#templatematch"><code>TemplateMatch</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#featurematch"><code>FeatureMatch</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#colormatch"><code>ColorMatch</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#ocr"><code>OCR</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#neuralnetworkclassify"><code>NeuralNetworkClassify</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#neuralnetworkdetect"><code>NeuralNetworkDetect</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#and"><code>And</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#or"><code>Or</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#custom"><code>Custom</code></a></p><p>动作action包括默认 <a href="https://maafw.com/docs/3.1-PipelineProtocol#donothing"><code>DoNothing</code></a> 。<br>可选的值：<a href="https://maafw.com/docs/3.1-PipelineProtocol#donothing"><code>DoNothing</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#click"><code>Click</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#longpress"><code>LongPress</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#swipe"><code>Swipe</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#multiswipe"><code>MultiSwipe</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#scroll"><code>Scroll</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#clickkey"><code>ClickKey</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#longpresskey"><code>LongPressKey</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#inputtext"><code>InputText</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#startapp"><code>StartApp</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#stopapp"><code>StopApp</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#stoptask"><code>StopTask</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#command"><code>Command</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#shell"><code>Shell</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#screencap"><code>Screencap</code></a> | <a href="https://maafw.com/docs/3.1-PipelineProtocol#custom-1"><code>Custom</code></a></p><p><code>next</code>接下来要执行的节点列表。可选，默认空。<br>按顺序识别 next 中的每个节点，只执行第一个识别到的</p><p><code>on_error</code> 当<strong>本节点</strong>内的 <code>next</code> 列表中没有任何节点命中并且循环识别超时，或动作执行失败后，接下来会执行该列表中的节点。可选，默认空。</p><blockquote><p>这里存在一点疑惑,<code>next</code> 列表中没有任何节点命中并且循环识别超时到底是走on_error列表还是直接中止</p></blockquote><p><code>rate_limit</code>: <em>uint</em><br>识别速率限制，单位毫秒。可选，默认 1000 。<br>每轮识别最低消耗 <code>rate_limit</code> 毫秒，不足的时间将会 sleep 等待。</p><p><code>timeout</code>: <em>int</em><br>当前<strong>节点</strong>的 <code>next</code> 列表识别的超时时间，毫秒。可选，默认 20 * 1000（20 秒）</p><p><code>repeat</code>: <em>uint</em><br>动作重复执行次数。可选，默认 1 ，即不重复。</p><p><code>max_hit</code>: <em>uint</em><br>该节点最多可被识别成功多少次。可选，默认 UINT_MAX ，即无限制。<br>若超过该次数，其他 node 的 next 列表中的该 node 会被跳过，既不会被识别也不会被执行</p><p><code>anchor</code>: <em>string</em> | <em>list<string, ></em> | <em>object</em><br>锚点名称。可选，默认空。<br>当节点识别命中并执行动作后，会将该锚点名设置为对应的节点（无论动作成功或失败）。多个节点可设置同一个锚点名，后执行的会覆盖先执行的。</p><p>支持三种格式：</p><ul><li><p><strong>字符串</strong>：<code>&quot;anchor&quot;: &quot;MyAnchor&quot;</code> - 将锚点设置为当前节点</p></li><li><p><strong>字符串数组</strong>：<code>&quot;anchor&quot;: [&quot;A&quot;, &quot;B&quot;]</code> - 将多个锚点都设置为当前节点</p></li><li><p><strong>对象</strong> <strong>💡 v5.7</strong>：<code>&quot;anchor&quot;: {&quot;A&quot;: &quot;TargetNode&quot;, &quot;B&quot;: &quot;&quot;}</code> - 将锚点 A 设置为 TargetNode，锚点 B 清除（空字符串表示清除该锚点）</p><p>在 <code>next</code> 或 <code>on_error</code> 中可通过 <code>[Anchor]</code> 属性引用该锚点，运行时会解析为最后设置该锚点的节点。如果引用的锚点未设置或已被清除，该节点将被跳过（不会执行）。</p></li></ul><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;NodeA&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;recognition&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;TemplateMatch&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;param&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="comment">// 识别相关字段放入 recognition.param 中，键和值均无变化</span></span><br><span class="line">                <span class="attr">&quot;template&quot;</span><span class="punctuation">:</span> <span class="string">&quot;A.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;roi&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="number">100</span><span class="punctuation">,</span> <span class="number">100</span><span class="punctuation">,</span> <span class="number">10</span><span class="punctuation">,</span> <span class="number">10</span><span class="punctuation">]</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Click&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;param&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="comment">// 动作相关字段放入 action.param 中，键和值均无变化</span></span><br><span class="line">                <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;XXX&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// 非 recognition 和 action 的字段与 v1 无变化</span></span><br><span class="line">        <span class="attr">&quot;next&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;NodeB&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;pre_delay&quot;</span><span class="punctuation">:</span> <span class="number">1000</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>类型放入 <code>type</code> 字段，其余参数放入 <code>param</code> 字段中</p><h2 id="PIV2协议"><a href="#PIV2协议" class="headerlink" title="PIV2协议"></a>PIV2协议</h2><blockquote><p>所谓 <code>ProjectInterface</code>，即 MaaFramework 的一个标准化的项目结构声明，该声明目前包含 <code>interface.json</code> 一个文件。通过定义 PI，你可以使用 MaaFramework 的各种衍生工具。因此，即使你打算纯粹依靠通用编程语言集成，也建议定义包含基础信息的 PI。</p></blockquote><p>一般可以通过PI定义项目信息以及UI展示。一般的UI项目会展示任务信息以及一些参数。<a href="https://maafw.com/docs/3.3-ProjectInterfaceV2">3.3 Project Interface V2 协议 | MaaFramework</a></p><h1 id="在已有应用中添加节点-任务"><a href="#在已有应用中添加节点-任务" class="headerlink" title="在已有应用中添加节点/任务"></a>在已有应用中添加节点/任务</h1><h1 id="个人开发基于MAA的应用"><a href="#个人开发基于MAA的应用" class="headerlink" title="个人开发基于MAA的应用"></a>个人开发基于MAA的应用</h1><p>可以使用<a href="https://github.com/MaaXYZ/MaaPracticeBoilerplate">MaaXYZ/MaaPracticeBoilerplate: MaaFramework 通用项目模板</a></p><p>以及参考资料<a href="https://github.com/MaaXYZ/MaaPracticeBoilerplate/blob/main/docs/zh_cn/develop/how_to_develop.md">how_to_develop</a></p><p>目前maafw提供了python,go,nodejs,c#以及rust等语言的binding,可以结合这些框架开发一些客户端,目前已经有<a href="https://github.com/MaaXYZ/MFAAvalonia">MaaXYZ/MFAAvalonia: 基于 Avalonia 的 MAAFramework 通用 GUI 项目</a>，<a href="https://github.com/MistEO/MXU">MistEO/MXU: MaaFramework Next UI</a>等通用UI</p><h1 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h1><ol><li><a href="https://github.com/MaaAssistantArknights/MaaAssistantArknights">MaaAssistantArknights/MaaAssistantArknights: 《明日方舟》小助手，全日常一键长草！| A one-click tool for the daily tasks of Arknights, supporting all clients.</a></li><li><a href="https://maafw.com/">MaaFramework</a></li><li><a href="https://docs.maa.plus/zh-cn/develop/development.html">开发指南 | 开发文档 | MAA 文档站</a></li><li><a href="https://hub.maafw.com/">MaaHub</a></li><li><a href="https://github.com/MAA1999/M9A">MAA1999/M9A: 重返未来：1999 小助手 | Assistant For Reverse: 1999</a></li><li><a href="https://github.com/MistEO/MXU">MistEO/MXU: MaaFramework Next UI</a></li><li><a href="https://github.com/1bananachicken/MaaNTE">1bananachicken/MaaNTE: MaaNTE. Nevertheless to Everless automatic assistant 异环小助手</a></li><li><a href="https://github.com/MaaXYZ/MaaFramework/tree/main?tab=readme-ov-file#开发工具">MaaXYZ/MaaFramework: 基于图像识别的自动化黑盒测试框架 | An automation black-box testing framework based on image recognition</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/06/04/%E4%BB%8E%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%88%B0%E6%B8%B8%E6%88%8F%E8%84%9A%E6%9C%AC-%E4%BD%BF%E7%94%A8Maafw%E5%BC%80%E5%8F%91/</id>
    <link href="https://www.sekyoro.top/2026/06/04/%E4%BB%8E%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%88%B0%E6%B8%B8%E6%88%8F%E8%84%9A%E6%9C%AC-%E4%BD%BF%E7%94%A8Maafw%E5%BC%80%E5%8F%91/"/>
    <published>2026-06-04T08:18:03.000Z</published>
    <summary>
      <![CDATA[<p>起因是想用自动化的流程做一些游戏任务,右面发现了MAA相关资源并尝试学习开发一些流程。这里总结一下踩坑和经验。<br>]]>
    </summary>
    <title>从自动化测试到游戏脚本:使用Maafw开发</title>
    <updated>2026-06-04T10:57:52.970Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="AI Coding" scheme="https://www.sekyoro.top/tags/AI-Coding/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近正式开始使用Claude Code/Codex/Gemini CLI+CC-Switch+API的Agent Coding的方式构建项目。之前是直接用Copilot和Trae这种跟IDE结合更紧密,也提供了编辑文件,浏览目录以及测试命令的功能，但Claude Code明显提供了更好的体验以及更先进的AI编程工程范式。<br>这里做一些实战总结,主要是Claude Code使用上。<br><span id="more"></span></p><h1 id="Claude-Code是什么"><a href="#Claude-Code是什么" class="headerlink" title="Claude Code是什么"></a>Claude Code是什么</h1><p>Claude Code 是一个代理编码工具，可以读取你的代码库、编辑文件、运行命令，并与你的开发工具集成。可在终端、IDE、桌面应用和浏览器中使用。Claude Code 是一个由 AI 驱动的编码助手，可帮助你构建功能、修复错误和自动化开发任务。它理解你的整个代码库，可以跨多个文件和工具工作以完成任务。</p><blockquote><p>考虑到国内充值付费问题,使用方式可参考<a href="https://proanimer.com/2026/05/27/终端接管、自主降本！国内丝滑落地-claude-code-codex-agent-编程的/">国内丝滑落地 Claude Code/Codex Agent 编程的硬核指南</a>.使用第三方中转站</p></blockquote><h2 id="如何工作"><a href="#如何工作" class="headerlink" title="如何工作"></a>如何工作</h2><p>通过Agent循环工作,而这个循环中包含模型推理和工具调用。ClaudeCode提供工具、上下文管理以及执行环境。</p><p><img data-src="https://files.seeusercontent.com/2026/05/27/Fb9r/image-20260528000942782.png" alt="image-20260528000942782"></p><p><strong>模型</strong></p><p>执行引擎。在会话期间使用 <code>/model</code> 切换或使用 <code>claude --model &lt;name&gt;</code> 启动。</p><p><strong>工具</strong></p><p>没有工具，Claude 只能用文本回应。有了工具，Claude 可以采取行动：读取您的代码、编辑文件、运行命令、搜索网络并与外部服务交互.</p><p>内置工具通常分为五个类别，每个类别代表不同类型的代理能力。</p><div class="table-container"><table><thead><tr><th style="text-align:left">类别</th><th style="text-align:left">Claude 可以做什么</th></tr></thead><tbody><tr><td style="text-align:left"><strong>文件操作</strong></td><td style="text-align:left">读取文件、编辑代码、创建新文件、重命名和重新组织</td></tr><tr><td style="text-align:left"><strong>搜索</strong></td><td style="text-align:left">按模式查找文件、使用正则表达式搜索内容、探索代码库</td></tr><tr><td style="text-align:left"><strong>执行</strong></td><td style="text-align:left">运行 shell 命令、启动服务器、运行测试、使用 git</td></tr><tr><td style="text-align:left"><strong>网络</strong></td><td style="text-align:left">搜索网络、获取文档、查找错误消息</td></tr><tr><td style="text-align:left"><strong>代码智能</strong></td><td style="text-align:left">编辑后查看类型错误和警告、跳转到定义、查找引用（需要<a href="https://code.claude.com/docs/zh-CN/discover-plugins#code-intelligence">代码智能插件</a>）</td></tr></tbody></table></div><p>工具调用时机本质还是利用模型根据提示和沿途学到的内容选择使用哪些。</p><blockquote><p>可以使用 <a href="https://code.claude.com/docs/zh-CN/skills">skills</a> 扩展 Claude 知道的内容、使用 <a href="https://code.claude.com/docs/zh-CN/mcp">MCP</a> 连接到外部服务、使用 <a href="https://code.claude.com/docs/zh-CN/hooks">hooks</a> 自动化工作流，以及将任务卸载给 <a href="https://code.claude.com/docs/zh-CN/sub-agents">subagents</a>。</p></blockquote><p><strong>平台集成</strong></p><p>本身可以直接在终端运行，也可以跟<a href="https://code.claude.com/docs/zh-CN/vs-code">VS Code</a>,<a href="https://code.claude.com/docs/zh-CN/jetbrains">JetBrains IDEs </a>结合</p><p>事实上，可以通过终端、<a href="https://code.claude.com/docs/zh-CN/desktop">桌面应用</a>、<a href="https://code.claude.com/docs/zh-CN/vs-code">IDE 扩展</a>、<a href="https://claude.ai/code">claude.ai/code</a>、<a href="https://code.claude.com/docs/zh-CN/remote-control">远程控制</a>、<a href="https://code.claude.com/docs/zh-CN/slack">Slack</a> 和 <a href="https://code.claude.com/docs/zh-CN/github-actions">CI/CD 管道</a>访问 Claude Code。</p><p>claude配置主要是两个地方,一个是用户目录,另一个是项目目录</p><p><img data-src="https://files.seeusercontent.com/2026/05/27/Ye0p/image-20260528003358318.png" alt="用户目录"></p><h3 id="扩展Claude"><a href="#扩展Claude" class="headerlink" title="扩展Claude"></a>扩展Claude</h3><p>使用 CLAUDE.md 文件为 Claude 提供持久指令，并让 Claude 通过自动记忆功能自动积累学习内容。</p><p>每个 Claude Code 会话都从一个全新的上下文窗口开始。两种机制可以跨会话传递知识：</p><ul><li><strong>CLAUDE.md 文件</strong>：你编写的指令，为 Claude 提供持久上下文<a href="https://code.claude.com/docs/zh-CN/memory">Claude 如何记住你的项目 - Claude Code Docs</a></li><li><strong>自动记忆</strong>：Claude 根据你的更正和偏好自己编写的笔记</li></ul><div class="table-container"><table><thead><tr><th style="text-align:left">CLAUDE.md 文件</th><th style="text-align:left">自动记忆</th><th></th></tr></thead><tbody><tr><td style="text-align:left"><strong>谁编写</strong></td><td style="text-align:left">你</td><td>Claude</td></tr><tr><td style="text-align:left"><strong>包含内容</strong></td><td style="text-align:left">指令和规则</td><td>学习和模式</td></tr><tr><td style="text-align:left"><strong>范围</strong></td><td style="text-align:left">项目、用户或组织</td><td>每个工作树，跨 worktrees 共享</td></tr><tr><td style="text-align:left"><strong>加载到</strong></td><td style="text-align:left">每个会话</td><td>每个会话（前 200 行或 25KB）</td></tr><tr><td style="text-align:left"><strong>用于</strong></td><td style="text-align:left">编码标准、工作流、项目架构</td><td>构建命令、调试见解、Claude 发现的偏好</td></tr></tbody></table></div><blockquote><p>Claude Code 读取 <code>CLAUDE.md</code>，而不是 <code>AGENTS.md</code>。如果你的存储库已经为其他编码代理使用 <code>AGENTS.md</code>，创建一个导入它的 <code>CLAUDE.md</code></p></blockquote><ol><li>加载时机。在每个会话开始时加载到上下文窗口中。CLAUDE.md 文件无论长度如何都完整加载。</li><li>语法规则。通过<code>@</code>引用，每个 CLAUDE.md 文件目标在 200 行以下；编写具体到足以验证的指令；模块化加载。</li></ol><blockquote><p>自动记忆让 Claude 跨会话积累知识，无需你编写任何内容。Claude 在工作时为自己保存笔记：构建命令、调试见解、架构笔记、代码样式偏好和工作流习惯。Claude 不会每个会话都保存内容。它根据信息在未来对话中是否有用来决定什么值得记住。</p></blockquote><p>本质是存储在<code>~/.claude/projects/&lt;project&gt;/memory/</code>的各种文件,比如</p><figure class="highlight jboss-cli"><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></pre></td><td class="code"><pre><span class="line">~<span class="string">/.claude/projects/</span>&lt;project&gt;<span class="string">/memory/</span></span><br><span class="line">├── MEMORY.md          <span class="comment"># 简洁索引，加载到每个会话</span></span><br><span class="line">├── debugging.md       <span class="comment"># 关于调试模式的详细笔记</span></span><br><span class="line">├── api-conventions.md <span class="comment"># API 设计决策</span></span><br><span class="line">└── <span class="string">...</span>                <span class="comment"># Claude 创建的任何其他主题文件</span></span><br></pre></td></tr></table></figure><p><code>MEMORY.md</code> 的前 200 行或前 25KB（以先到者为准）在每次对话开始时加载。超过该阈值的内容在会话开始时不加载。Claude 在你的会话中读取和写入记忆文件。当你在 Claude Code 界面中看到”Writing memory”或”Recalled memory”时，Claude 正在主动更新或读取 <code>~/.claude/projects/&lt;project&gt;/memory/</code>。</p><h3 id="claude目录"><a href="#claude目录" class="headerlink" title=".claude目录"></a>.claude目录</h3><p>Claude Code 读取 CLAUDE.md、settings.json、hooks、skills、commands、subagents、rules 和自动内存的位置。</p><p><code>.claude</code>目录包括个人的<code>~/.claude</code>以及项目的<code>.claude</code>.<code>~/.claude</code> 中的文件是个人配置，适用于所有项目。</p><div class="table-container"><table><thead><tr><th style="text-align:left">您想要</th><th style="text-align:left">编辑</th><th style="text-align:left">范围</th><th style="text-align:left">参考</th></tr></thead><tbody><tr><td style="text-align:left">为 Claude 提供项目上下文和约定</td><td style="text-align:left"><code>CLAUDE.md</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/memory">内存</a></td></tr><tr><td style="text-align:left">允许或阻止特定工具调用</td><td style="text-align:left"><code>settings.json</code> <code>permissions</code> 或 <code>hooks</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permissions">权限</a>、<a href="https://code.claude.com/docs/zh-CN/hooks">Hooks</a></td></tr><tr><td style="text-align:left">在工具调用前后运行脚本</td><td style="text-align:left"><code>settings.json</code> <code>hooks</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/hooks">Hooks</a></td></tr><tr><td style="text-align:left">为会话设置环境变量</td><td style="text-align:left"><code>settings.json</code> <code>env</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/settings#available-settings">设置</a></td></tr><tr><td style="text-align:left">将个人覆盖保留在 git 之外</td><td style="text-align:left"><code>settings.local.json</code></td><td style="text-align:left">仅项目</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/settings#settings-files">设置范围</a></td></tr><tr><td style="text-align:left">添加使用 <code>/name</code> 调用的提示或功能</td><td style="text-align:left"><code>skills/&lt;name&gt;/SKILL.md</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/skills">Skills</a></td></tr><tr><td style="text-align:left">定义具有自己工具的专门 subagent</td><td style="text-align:left"><code>agents/*.md</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/sub-agents">Subagents</a></td></tr><tr><td style="text-align:left">通过 MCP 连接外部工具</td><td style="text-align:left"><code>.mcp.json</code></td><td style="text-align:left">仅项目</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/mcp">MCP</a></td></tr><tr><td style="text-align:left">更改 Claude 格式化响应的方式</td><td style="text-align:left"><code>output-styles/*.md</code></td><td style="text-align:left">项目或全局</td><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/output-styles">输出样式</a></td></tr></tbody></table></div><h3 id="上下文窗口与prompt-caching"><a href="#上下文窗口与prompt-caching" class="headerlink" title="上下文窗口与prompt caching"></a>上下文窗口与prompt caching</h3><p>运行 <code>/context</code> 以获取按类别的实时分解和优化建议。运行 <code>/memory</code> 以检查在启动时加载了哪些 CLAUDE.md 和自动内存文件。<a href="https://code.claude.com/docs/zh-CN/context-window">探索上下文窗口</a>查看一个上下文的组成</p><p>既然每次跟模型的对话都会重发一些内容, 有哪些方式可以优化以节约成本呢。目前常用的技术还是prompt caching和<code>/compact</code>裁剪</p><blockquote><p>每次在 Claude Code 中发送消息时，它都会发出新的 API 请求。模型在请求之间不记得任何东西，所以 Claude Code 重新发送完整的上下文：系统提示、您的项目上下文、每条先前的消息和工具结果，以及您的新消息。新内容附加在末尾，这意味着每个请求的大部分与前一个请求相同。Prompt caching 是 API 避免重新处理未更改部分的方式。</p></blockquote><p>为了充分利用前缀匹配，Claude Code 组织每个请求，使回合之间很少更改的内容首先出现：</p><div class="table-container"><table><thead><tr><th style="text-align:left">层</th><th style="text-align:left">内容</th><th style="text-align:left">更改时间</th></tr></thead><tbody><tr><td style="text-align:left">系统提示</td><td style="text-align:left">核心指令、工具定义、输出样式</td><td style="text-align:left">MCP 服务器连接或断开连接，或 Claude Code 升级</td></tr><tr><td style="text-align:left">项目上下文</td><td style="text-align:left">CLAUDE.md、自动内存、无范围规则</td><td style="text-align:left">会话开始，或在 <code>/clear</code> 或 <code>/compact</code> 之后</td></tr><tr><td style="text-align:left">对话</td><td style="text-align:left">您的消息、Claude 的响应、工具结果</td><td style="text-align:left">每个回合</td></tr></tbody></table></div><h2 id="权限模式"><a href="#权限模式" class="headerlink" title="权限模式"></a>权限模式</h2><p>每种模式在便利性和监督之间做出不同的权衡。</p><div class="table-container"><table><thead><tr><th style="text-align:left">模式</th><th style="text-align:left">无需询问即可运行的操作</th><th style="text-align:left">最适合</th></tr></thead><tbody><tr><td style="text-align:left"><code>default</code></td><td style="text-align:left">仅读取</td><td style="text-align:left">入门、敏感工作</td></tr><tr><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permission-modes#auto-approve-file-edits-with-acceptedits-mode"><code>acceptEdits</code></a></td><td style="text-align:left">读取、文件编辑和常见文件系统命令（<code>mkdir</code>、<code>touch</code>、<code>mv</code>、<code>cp</code> 等）</td><td style="text-align:left">迭代您正在审查的代码</td></tr><tr><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permission-modes#analyze-before-you-edit-with-plan-mode"><code>plan</code></a></td><td style="text-align:left">仅读取</td><td style="text-align:left">在更改代码库前进行探索</td></tr><tr><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permission-modes#eliminate-prompts-with-auto-mode"><code>auto</code></a></td><td style="text-align:left">所有操作，带后台安全检查</td><td style="text-align:left">长时间任务、减少提示疲劳</td></tr><tr><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permission-modes#allow-only-pre-approved-tools-with-dontask-mode"><code>dontAsk</code></a></td><td style="text-align:left">仅预先批准的工具</td><td style="text-align:left">锁定的 CI 和脚本</td></tr><tr><td style="text-align:left"><a href="https://code.claude.com/docs/zh-CN/permission-modes#skip-all-checks-with-bypasspermissions-mode"><code>bypassPermissions</code></a></td><td style="text-align:left">所有操作，带后台安全检查</td><td style="text-align:left">仅隔离容器和 VM</td></tr></tbody></table></div><p><strong>在会话期间</strong>：按 <code>Shift+Tab</code> 循环切换 <code>default</code> → <code>acceptEdits</code> → <code>plan</code>。当前模式显示在状态栏中。并非每种模式都在默认循环中：</p><ul><li><code>auto</code>：当您的账户满足 <a href="https://code.claude.com/docs/zh-CN/permission-modes#eliminate-prompts-with-auto-mode">auto mode 要求</a>时出现；循环到 auto 会显示一个选择加入提示，直到您接受它，或选择<strong>不，不再询问</strong>以从循环中移除 auto</li><li><code>bypassPermissions</code>：在您使用 <code>--permission-mode bypassPermissions</code>、<code>--dangerously-skip-permissions</code> 或 <code>--allow-dangerously-skip-permissions</code> 启动后出现；<code>--allow-</code> 变体将模式添加到循环中而不激活它</li><li><code>dontAsk</code>：永远不会出现在循环中；使用 <code>--permission-mode dontAsk</code> 设置它</li></ul><h2 id="会话管理"><a href="#会话管理" class="headerlink" title="会话管理"></a>会话管理</h2><p>会话是与项目目录关联的已保存对话。Claude Code 在您工作时将其本地存储，因此您可以从中断处恢复、分支以尝试不同的方法，或在任务之间切换。每次在一个项目目录启动<code>claude</code>都视为一次独立的会话,在<code>~/.claude/projects/</code>下有不同项目下的若干个会话。</p><p>对于会话,可以进行<strong>恢复</strong>,<strong>命名</strong>,分支会话,<strong>导出</strong>以及管理会话内的上下文.</p><p>恢复会话通过<code>/resume</code>进行选择,命名通过<code>/rename</code>,管理上下文通过<code>\clear</code>清除以及<code>/compact</code>压缩</p><h1 id="实操"><a href="#实操" class="headerlink" title="实操"></a>实操</h1><h2 id="扩展能力"><a href="#扩展能力" class="headerlink" title="扩展能力"></a>扩展能力</h2><ol><li>创建<code>commands</code>,在项目中<code>.claude/commands</code>下创建<code>*.md</code>文件</li></ol><p>Claude Code 里的 slash commands 大致分四类：</p><ul><li><strong>Built-in commands</strong>：Claude Code 自带的命令，如 <code>/help</code>、<code>/clear</code>、<code>/model</code></li><li><strong>Skills 形式的命令</strong>：通过 <code>SKILL.md</code> 定义，仍然可以用 <code>/name</code> 调用</li><li><strong>Plugin commands</strong>：安装 plugin 后带来的命令</li><li><strong>MCP prompts</strong>：由 MCP server 暴露出来的命令</li></ul><p><code>*.md</code>文件中包含内容,frontmatter 包含描述,允许的工具等,此外还有工作流程。</p><ol><li>更改memory</li></ol><p>包括<code>CLAUDE.md</code>和自动内存.</p><p><code>CLAUDE.md</code>包含内容如下。可以通过<code>/init</code>常见,<code>/memory</code>查看或编辑</p><ul><li>哪些目录最重要</li><li>哪些规则最容易被忽略</li><li>提交前必须做什么</li><li>哪些工具是本项目默认用法</li><li>哪些文件不要乱动</li><li>测试和验证的最低标准</li></ul><p>3.skiils</p><p>skill可以理解成：</p><ul><li>一个带 frontmatter 的 <code>SKILL.md</code></li><li>可附带脚本、模板、参考资料</li><li>会被 Claude 自动发现和按需加载</li><li>更适合长期复用的工作流能力</li></ul><p>skill基本结构</p><figure class="highlight text"><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></pre></td><td class="code"><pre><span class="line">skill-name/</span><br><span class="line">├── SKILL.md</span><br><span class="line">├── templates/</span><br><span class="line">├── scripts/</span><br><span class="line">└── references/</span><br></pre></td></tr></table></figure><p><code>SKILL.md</code>负责</p><ul><li>定义 skill 名称</li><li>说明 skill 在什么情况下应该触发</li><li><p>告诉 Claude 该怎么做</p></li><li><p><code>templates/</code>：输出模板</p></li><li><code>scripts/</code>：辅助脚本</li><li><code>references/</code>：参考规则或背景知识</li></ul><p>4.subagents</p><p>subagents 是 Claude Code 里做复杂任务拆分的关键能力。你可以把它理解成“主 Claude 把某个子任务交给一个更专业、上下文更独立的助手去做”。</p><p>subagent 具备这些特点：</p><ul><li>有自己的角色定位</li><li>有自己的上下文窗口</li><li>可以限制可用工具</li><li>可以使用单独的 system prompt</li><li>适合做任务拆分和专业分工</li></ul><div class="table-container"><table><thead><tr><th>价值</th><th>说明</th></tr></thead><tbody><tr><td>上下文隔离</td><td>避免主对话被复杂细节污染</td></tr><tr><td>专业分工</td><td>不同 agent 做不同任务</td></tr><tr><td>工具隔离</td><td>可以限制某个 agent 能做什么</td></tr><tr><td>可复用</td><td>适合团队共享常用角色</td></tr></tbody></table></div><div class="table-container"><table><thead><tr><th>类型</th><th>路径</th><th>作用域</th></tr></thead><tbody><tr><td>项目级</td><td><code>.claude/agents/</code></td><td>当前项目</td></tr><tr><td>用户级</td><td><code>~/.claude/agents/</code></td><td>所有项目</td></tr></tbody></table></div><p>subagent 文件通常是：</p><ol><li>YAML frontmatter</li><li>后面跟 Markdown 形式的 system prompt</li></ol><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">code-reviewer</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">Review</span> <span class="string">recent</span> <span class="string">changes</span> <span class="string">for</span> <span class="string">quality</span> <span class="string">issues</span></span><br><span class="line"><span class="attr">tools:</span> <span class="string">Read,</span> <span class="string">Grep,</span> <span class="string">Glob,</span> <span class="string">Bash</span></span><br><span class="line"><span class="attr">model:</span> <span class="string">inherit</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><blockquote><ul><li><code>subagents</code>：主 Claude 委派一个边界清晰的子任务，等它把结果带回来</li><li><code>Agent Teams</code>：多个 Claude Code 实例协作，彼此有独立上下文窗口，还能直接通信</li></ul></blockquote><p>本质上是分配一个新角色,它有独立的上下文,职责不同,执行完后能返回结果。</p><p>5.mcp使用</p><p>如果没有 MCP，Claude 只能基于你提供的上下文回答。<br>有了 MCP，它可以：</p><ul><li>获取实时数据</li><li>调用外部工具</li><li>访问项目外的信息源</li><li>把结果带回当前工作流</li></ul><p><strong>MCP 常见应用场景</strong></p><ul><li>GitHub PR / issue 查询</li><li>数据库读写</li><li>文件系统访问</li><li>Slack / Docs / 其他 SaaS 工具集成</li></ul><p>在<code>.mcp.json</code>中配置</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;database&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;@modelcontextprotocol/server-database&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;DATABASE_URL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;postgresql://user:pass@localhost/mydb&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>6.hooks</p><p>hooks 是 Claude Code 的“事件触发自动化”机制。它允许你在某些时机自动做点事情，例如：</p><ul><li>调工具前先检查风险</li><li>写完文件后自动格式化</li><li>提交前跑测试</li><li>结束前记录日志或上下文</li></ul><p>可以把 hook 理解成一条规则：</p><ol><li>某个事件发生</li><li>匹配某个工具或场景</li><li>自动执行一个动作</li></ol><p>这些动作可以是：</p><ul><li>shell command</li><li>HTTP webhook</li><li>prompt 型判断</li><li>agent 型评估</li></ul><p>hooks 最大的价值，是把“你本来每次都要手动做的检查”变成自动流程。</p><p><strong>配置位置</strong></p><ul><li><code>~/.claude/settings.json</code>：用户级，对所有项目生效</li><li><code>.claude/settings.json</code>：项目级，适合团队共享</li><li><code>.claude/settings.local.json</code>：本地项目配置，不建议提交</li><li>plugin 内的 <code>hooks/hooks.json</code></li></ul><p>一般结构</p><figure class="highlight prolog"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;hooks&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;EventName&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;matcher&quot;</span>: <span class="string">&quot;ToolPattern&quot;</span>,</span><br><span class="line">        <span class="string">&quot;hooks&quot;</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="string">&quot;type&quot;</span>: <span class="string">&quot;command&quot;</span>,</span><br><span class="line">            <span class="string">&quot;command&quot;</span>: <span class="string">&quot;your-command-here&quot;</span>,</span><br><span class="line">            <span class="string">&quot;timeout&quot;</span>: <span class="number">60</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>hooks有匹配工具,类型,执行的命令等</p><figure class="highlight json"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;SessionStart&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;startup&quot;</span><span class="punctuation">,</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/session-start.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">30</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;clear&quot;</span><span class="punctuation">,</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/session-start.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">30</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;compact&quot;</span><span class="punctuation">,</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/session-start.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">30</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;PreToolUse&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Task&quot;</span><span class="punctuation">,</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/inject-subagent-context.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">30</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Agent&quot;</span><span class="punctuation">,</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/inject-subagent-context.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">30</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;UserPromptSubmit&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">     <span class="punctuation">&#123;</span></span><br><span class="line">       <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">         <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python .claude/hooks/inject-workflow-state.py&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">15</span></span><br><span class="line">         <span class="punctuation">&#125;</span></span><br><span class="line">       <span class="punctuation">]</span></span><br><span class="line">     <span class="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>7.plugins</p><p><a href="https://github.com/anthropics/claude-plugins-official">anthropics/claude-plugins-official</a></p><p>plugin 通常会组合这些内容：</p><ul><li>commands</li><li>skills</li><li>subagents</li><li>hooks</li><li><code>.mcp.json</code></li><li>辅助脚本与模板</li></ul><p>所以它特别适合：</p><ul><li>团队统一工作流</li><li>跨项目复用</li><li>把一套最佳实践做成可分发单元</li></ul><p>plugin配置如下</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line">my-plugin/</span><br><span class="line">├── .claude-plugin/</span><br><span class="line">│   └── plugin.json</span><br><span class="line">├── commands/</span><br><span class="line">├── agents/</span><br><span class="line">├── skills/</span><br><span class="line">├── hooks/</span><br><span class="line">├── .mcp.json</span><br><span class="line">├── scripts/</span><br><span class="line">├── templates/</span><br><span class="line">└── docs/</span><br></pre></td></tr></table></figure><p>8.checkpoints</p><p>可以把 checkpoint 理解成当前会话状态的快照，通常包括：</p><ul><li>对话消息</li><li>文件改动</li><li>工具使用历史</li><li>会话上下文</li></ul><p>当你需要回退时，就使用 <code>rewind</code> 返回到某个 checkpoint。Claude Code 会自动创建 checkpoints，所以你不需要手动先“存档”才能用。</p><p>常见有这些：</p><ol><li><strong>Restore code and conversation</strong>：代码和对话都回退</li><li><strong>Restore conversation</strong>：只回退对话</li><li><strong>Restore code</strong>：只回退代码</li><li><strong>Summarize from here</strong>：从这一点开始压缩总结，释放上下文窗口</li><li><strong>Never mind</strong>：取消</li></ol><figure class="highlight text"><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></pre></td><td class="code"><pre><span class="line">先让 Claude 改</span><br><span class="line">→ 如果结果好，继续</span><br><span class="line">→ 如果不满意，/rewind</span><br><span class="line">→ 换一种实现路线</span><br></pre></td></tr></table></figure><p>这在这些任务里尤其好用：</p><ul><li>UI 重构</li><li>API 重构</li><li>auth / permission 变更</li><li>大批量文档整理</li></ul><p>9.高级功能</p><p>权限模式</p><div class="table-container"><table><thead><tr><th>模式</th><th>适合什么</th></tr></thead><tbody><tr><td><code>default</code></td><td>日常安全使用</td></tr><tr><td><code>acceptEdits</code></td><td>希望编辑流畅一些</td></tr><tr><td><code>plan</code></td><td>只想分析，不想改</td></tr><tr><td><code>dontAsk</code></td><td>非交互脚本</td></tr><tr><td><code>bypassPermissions</code></td><td>可信环境中的强自动化</td></tr><tr><td><code>auto</code></td><td>有更高自动化诉求、且明确接受风险</td></tr></tbody></table></div><p>通过<code>--dangerously-skip-permissions</code>切换到<code>bypassPermissions</code>,可以通过<code>shift+tab</code>切换,也可以通过<code>claude --permission-mode plan</code>.也可以通过<code>\config</code>配置。</p><p><code>claude -p</code>输出模式适合shell脚本以及CI/CD等人物。此外还有后台任务<code>\background</code>让耗时任务后台跑，不阻塞当前会话.</p><p>10.CLI功能</p><div class="table-container"><table><thead><tr><th>命令</th><th>用途</th></tr></thead><tbody><tr><td><code>claude</code></td><td>打开交互模式</td></tr><tr><td><code>claude &quot;query&quot;</code></td><td>带初始问题进入 REPL</td></tr><tr><td><code>claude -p &quot;query&quot;</code></td><td>print mode，一次执行后退出</td></tr><tr><td><code>claude -c</code></td><td>继续最近一次会话</td></tr><tr><td><code>claude -r &quot;session&quot;</code></td><td>恢复指定 session</td></tr><tr><td><code>claude mcp</code></td><td>管理 MCP</td></tr><tr><td><code>claude agents</code></td><td>打开 Agent View，多 session 管理界面</td></tr><tr><td><code>claude agents --json</code></td><td>以 JSON 输出 Agent View session 列表，适合脚本、状态栏和 session picker</td></tr><tr><td><code>claude plugin</code></td><td>管理 plugins</td></tr><tr><td><code>claude remote-control</code></td><td>启动远程控制</td></tr><tr><td><code>claude auth status</code></td><td>查看登录状态</td></tr><tr><td><code>claude project purge [path]</code></td><td>清理某个项目的本地 Claude Code 状态，先用 <code>--dry-run</code> 预览</td></tr><tr><td><code>claude plugin prune</code></td><td>清理无主的自动安装 plugin 依赖</td></tr><tr><td><code>claude ultrareview [target]</code></td><td>在无头模式里运行 <code>/ultrareview</code>，适合 CI / PR gate</td></tr></tbody></table></div><div class="table-container"><table><thead><tr><th>flag</th><th>用途</th></tr></thead><tbody><tr><td><code>-p, --print</code></td><td>进入 print mode</td></tr><tr><td><code>-c, --continue</code></td><td>继续最近一次会话</td></tr><tr><td><code>-r, --resume</code></td><td>恢复指定 session</td></tr><tr><td><code>-n, --name</code></td><td>给 session 起名</td></tr><tr><td><code>-w, --worktree</code></td><td>在 worktree 中启动</td></tr><tr><td><code>--model</code></td><td>指定模型</td></tr><tr><td><code>--effort</code></td><td>指定思考强度；现在支持 <code>xhigh</code></td></tr><tr><td><code>--permission-mode</code></td><td>指定权限模式</td></tr><tr><td><code>--bare</code></td><td>以最小模式启动</td></tr><tr><td><code>--add-dir</code></td><td>加额外目录到工作上下文</td></tr><tr><td><code>--tmux</code></td><td>给 worktree / 多任务场景创建 tmux 会话</td></tr><tr><td><code>--exclude-dynamic-system-prompt-sections</code></td><td>排除系统提示中的动态段落，帮助 prompt cache 更稳定命中</td></tr></tbody></table></div><p>CLI 常见会和这些配置一起使用：</p><ul><li><code>--model</code></li><li><code>--fallback-model</code></li><li><code>--effort</code></li><li><code>--settings</code></li><li><code>--append-system-prompt</code></li></ul><p>示例：</p><figure class="highlight stylus"><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">claude <span class="attr">--model</span> opus <span class="string">&quot;design a caching strategy&quot;</span></span><br><span class="line">claude -<span class="selector-tag">p</span> <span class="attr">--fallback-model</span> sonnet <span class="string">&quot;summarize this diff&quot;</span></span><br><span class="line">claude <span class="attr">--append-system-prompt</span> <span class="string">&quot;Always explain tradeoffs&quot;</span> <span class="string">&quot;review this plan&quot;</span></span><br></pre></td></tr></table></figure><p><strong>工具与权限相关 flags</strong></p><ul><li><code>--permission-mode</code></li><li><code>--dangerously-skip-permissions</code></li><li><code>--allowedTools</code></li><li><code>--disallowedTools</code></li><li><code>--tools</code></li></ul><h2 id="学习资料"><a href="#学习资料" class="headerlink" title="学习资料"></a>学习资料</h2><ol><li><a href="https://learn.deeplearning.ai/courses/claude-code-a-highly-agentic-coding-assistant/lesson/66b35/introduction">Claude Code: A Highly Agentic Coding Assistant - DeepLearning.AI</a></li><li><a href="https://learn.deeplearning.ai/courses/spec-driven-development-with-coding-agents/lesson/15v9p3/feature-specification">Spec-Driven Development with Coding Agents - DeepLearning.AI</a></li><li><a href="https://github.com/lhfer/claude-howto-zh-cn">lhfer/claude-howto-zh-cn: Claude Code 中文全面上手指南。基于 luongnv89/claude-howto 本土化重写，面向中国小白用户，保留命令与配置兼容性，并附学习路径与本地化校验护栏。</a></li><li><a href="https://www.runoob.com/claude-code/claude-code-permission.html">Claude Code 权限配置 | 菜鸟教程</a></li><li><a href="https://code.claude.com/docs/zh-CN/overview">Claude Code Docs</a></li><li><a href="https://koialkaid.github.io/blog/posts/trellis-tech-sharing-materials/">Trellis 技术分享资料：大模型科普与 AI Coding 框架 | Koi’s Blog</a></li><li><a href="https://blog.devtang.com/2026/04/15/claude-code-autonomous-guide-zh/">让 Claude Code 在你睡觉时持续运行：完整实战指南 · 唐巧的博客</a></li><li>claude-&gt;/powerup</li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/05/27/Claude-Code-%E5%AE%9E%E6%88%98/</id>
    <link href="https://www.sekyoro.top/2026/05/27/Claude-Code-%E5%AE%9E%E6%88%98/"/>
    <published>2026-05-27T15:33:19.000Z</published>
    <summary>
      <![CDATA[<p>最近正式开始使用Claude Code/Codex/Gemini CLI+CC-Switch+API的Agent Coding的方式构建项目。之前是直接用Copilot和Trae这种跟IDE结合更紧密,也提供了编辑文件,浏览目录以及测试命令的功能，但Claude Code明显提供了更好的体验以及更先进的AI编程工程范式。<br>这里做一些实战总结,主要是Claude Code使用上。<br>]]>
    </summary>
    <title>Claude Code 实战</title>
    <updated>2026-05-31T15:02:40.826Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="computer network" scheme="https://www.sekyoro.top/tags/computer-network/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>介绍一些网络协议,主要内容来自极客时间趣谈网络协议.<br><span id="more"></span></p><h1 id="直播中用到的协议"><a href="#直播中用到的协议" class="headerlink" title="直播中用到的协议"></a>直播中用到的协议</h1><p>直播主要是视频,本质可以看作图片流。假设30帧的视频，像素1024*768，每个像素由RGB组成，每个8位，共24位。一秒钟的数据量30帧 × 1024 × 768 × 24 = 566,231,040Bits = 70,778,880Bytes达到4个G.</p><p>因此需要通过压缩或者说编解码减少数据传输，既然压缩，就要规定编解码协议</p><p>频流中的图片进行压缩，因为视频和图片有这样一些特点。</p><ol><li><strong>空间冗余</strong>：图像的相邻像素之间有较强的相关性，一张图片相邻像素往往是渐变的，不是突变的，没必要每个像素都完整地保存，可以隔几个保存一个，中间的用算法计算出来。</li><li><strong>时间冗余</strong>：视频序列的相邻图像之间内容相似。一个视频中连续出现的图片也不是突变的，可以根据已有的图片进行预测和推断。</li><li><strong>视觉冗余</strong>：人的视觉系统对某些细节不敏感，因此不会每一个细节都注意到，可以允许丢失一些数据。</li><li><strong>编码冗余</strong>：不同像素值出现的概率不同，概率高的用的字节少，概率低的用的字节多，类似<a href="https://zh.wikipedia.org/wiki/霍夫曼编码">霍夫曼编码（Huffman Coding）</a>的思路。</li></ol><h2 id="视频编码流派"><a href="#视频编码流派" class="headerlink" title="视频编码流派"></a>视频编码流派</h2><p>流媒体协议名称如下</p><ul><li><strong>名词系列一</strong>：AVI、MPEG、RMVB、MP4、MOV、FLV、WebM、WMV、ASF、MKV。 <strong>视频封装格式</strong></li><li><strong>名词系列二</strong>：H.261、 H.262、H.263、H.264、H.265。 <strong>视频编码格式</strong></li><li><p><strong>名词系列</strong>三：MPEG-1、MPEG-2、MPEG-4、MPEG-7。 <strong>MPEG指定的标准</strong></p></li><li><p>流派一：ITU（International Telecommunications Union）的VCEG（Video Coding Experts Group），这个称为<strong>国际电联下的VCEG</strong>。既然是电信，可想而知，他们最初做视频编码，主要侧重传输。名词系列二，就是这个组织制定的标准。</p></li><li>流派二：ISO（International Standards Organization）的MPEG（Moving Picture Experts Group），这个是<strong>ISO旗下的MPEG</strong>，本来是做视频存储的。例如，编码后保存在VCD和DVD中。当然后来也慢慢侧重视频传输了。名词系列三，就是这个组织制定的标准。</li></ul><p>后来，ITU-T（国际电信联盟电信标准化部门，ITU Telecommunication Standardization Sector）与MPEG联合制定了H.264/MPEG-4 AVC</p><h3 id="视频文件介绍"><a href="#视频文件介绍" class="headerlink" title="视频文件介绍"></a>视频文件介绍</h3><p>简单来说，一个视频文件是由“集装箱（封装格式）”<strong>和包装在里面的</strong>“货物（编码格式）”共同组成的</p><p>视频文件后缀名（如 <code>.mp4</code>、<code>.mkv</code>、<code>.avi</code>、<code>.mov</code>），在技术上被称为<strong>封装格式</strong>（Container Format）。</p><ul><li><strong>它的本质</strong>：它就像一个<strong>集装箱</strong>或<strong>文件夹</strong>。</li><li><strong>它的作用</strong>：负责把<strong>视频轨道</strong>（只有画面）、<strong>音频轨道</strong>（只有声音）、<strong>字幕轨道</strong>以及视频的发布时间、导演等元数据（Metadata）打包、同步并粘合在一起，变成一个独立的文件。</li></ul><p><strong>常见的封装格式</strong></p><p><strong>MP4</strong>：目前最流行的封装格式，兼容性最强。无论是手机、电脑、电视、网页、汽车中控，几乎 100% 都能完美播放。</p><p><strong>MKV</strong>：它的特点是<strong>容纳能力极强</strong>，一个文件里可以塞进好几种语言的音轨、十几国语言的特效字幕，电影高清爱好者最喜欢它。</p><p><strong>MOV</strong>：苹果公司（Apple）开发的集装箱，苹果生态和专业影视剪辑（如 ProRes 编码）的常用格式。</p><p><strong>AVI</strong> ：微软（Microsoft）推出的一种<strong>封装格式</strong>。</p><p><strong>视频封装格式（也就是视频文件集装箱/后缀名）</strong></p><p>它们不负责压缩画面，只负责把<strong>视频（画面）</strong>、<strong>音频（声音）\</strong>和*<em>字幕<strong>打包组合成一个文件。这 10 个格式记录了从 1990 年代 PC 刚兴起，到如今移动互联网、网页高清视频的</strong>整个多媒体进化史*</em>。</p><p>阵营一：现代全能霸主（目前最主流）</p><ol><li>MP4 (<code>.mp4</code>) —— 地表最强兼容性</li></ol><ul><li><strong>背景</strong>：由顶级组织 MPEG 推出（技术学名 MPEG-4 Part 14）。</li><li><strong>特点</strong>：<strong>没有任何缺点就是它最大的优点</strong>。它完美平衡了文件体积、画质和网络流媒体传输。</li><li><strong>现状</strong>：绝对的统治者。不管是苹果、安卓、Windows、电视、网页还是车载系统，100% 完美支持，网络上 90% 的视频都是它。</li></ul><ol><li>MKV (<code>.mkv</code>) —— 高清电影收藏家</li></ol><ul><li><strong>背景</strong>：著名的开源开放封装格式（Matroska）。</li><li><strong>特点</strong>：<strong>号称“万能集装箱”</strong>。它最大的本事是“极能塞”。一个 MKV 文件里可以塞进好几条不同语言的音轨（国语、粤语、英语）、十几国语言的特效字幕，并且支持最新的高性能编码。</li><li><strong>现状</strong>：电影发烧友、动漫高清资源、PT 下载的最爱。</li></ul><p>阵营二：互联网流媒体阵营（为了在线看视频而生）</p><ol><li>FLV (<code>.flv</code>) —— 网页网页视频的青春</li></ol><ul><li><strong>背景</strong>：Adobe 公司当年为了配合 <strong>Flash 播放器</strong>推出的格式。</li><li><strong>特点</strong>：体积小、加载快。在那个网络很慢的时代，它实现了“不需要下载完，点开就能在线看”的功能。</li><li><strong>现状</strong>：随着网页端 Flash 插件被历史彻底淘汰，FLV 已经退出了舞台，但在直播领域（如 HTTP-FLV 协议）仍有部分残留。</li></ul><ol><li>WebM (<code>.webm</code>) —— 现代网页与动图杀手</li></ol><ul><li><strong>背景</strong>：由 <strong>Google</strong> 牵头主导的开源免专利费网页视频格式。</li><li><strong>特点</strong>：天生为了现代 HTML5 网页而生。它可以用极小的体积实现超高清画质，并且<strong>完美支持动态透明通道（Alpha Channel）</strong>。</li><li><strong>现状</strong>：现在网页上的高画质动态表情包（取代低清 GIF）、网页背景短视频，大多是 <code>.webm</code> 格式。</li></ul><p>阵营三：老牌操作系统巨头（当年的门户之见）</p><ol><li>MOV (<code>.mov</code>) —— 苹果生态的基石</li></ol><ul><li><strong>背景</strong>：<strong>苹果公司（Apple）</strong> 开发的专用多媒体格式。</li><li><strong>特点</strong>：结构设计极其先进（后来的 MP4 就是参考 MOV 的架构制定的）。它能完美容纳影视工业级的<strong>高码率无损/半无损编码</strong>（如 Apple ProRes）。</li><li><strong>现状</strong>：iPhone 录制高质量视频、专业影视后期剪辑、Mac 生态的首选。</li></ul><ol><li>WMV (<code>.wmv</code>) —— 微软早期的视频王牌</li></ol><ul><li><strong>背景</strong>：<strong>微软（Microsoft）</strong> 专门为了和苹果 MOV、以及 Real 竞争推出的网络流媒体格式。</li><li><strong>特点</strong>：在 Windows 系统上拥有极高的兼容性，压缩率在当年很优秀，保护版权的 DRM 机制很严。</li><li><strong>现状</strong>：基本属于历史遗产，现在的微软自己也全面倒向了 MP4。</li></ul><ol><li>ASF (<code>.asf</code>) —— WMV 的底层外壳</li></ol><ul><li><strong>背景</strong>：微软推出的高级流格式（Advanced Systems Format）。</li><li><strong>特点</strong>：严格来说，<strong>ASF 是集装箱，而 WMV 是里面的货物</strong>。ASF 是微软为了在网络上传输音视频而设计的。</li><li><strong>现状</strong>：极为少见，偶尔在早期的网络直播或老旧微软系统中能看到。</li></ul><p>阵营四：历史的眼泪（基本已被淘汰）</p><ol><li>AVI (<code>.avi</code>) —— 1990年代的原生巨兽</li></ol><ul><li><strong>背景</strong>：微软在 1992 年（Windows 3.1 时代）推出的极其古老的格式。</li><li><strong>特点</strong>：架构太老。它不支持流媒体点播（必须全部下载完才能播放）、不支持特效字幕、不支持动态帧率。</li><li><strong>现状</strong>：已经彻底落伍。现在除了某些特殊行业的老设备、行车记录仪外，基本见不到了。</li></ul><ol><li>RMVB / RM (<code>.rmvb</code>) —— 中国互联网初期的“盗版电影之王”</li></ol><ul><li><strong>背景</strong>：RealNetworks 公司推出的格式。</li><li><strong>特点</strong>：那个用电话线拨号上网、网速只有几百 KB 的时代，RMVB 靠着惊人的压缩率实现了“小体积、能看清”。它统治了当年的电影下载网站（如各种影音先锋、天天动听时代）。</li><li><strong>现状</strong>：由于它闭源、收费，且随着网速变快、高清时代来临，其画质上限不足的缺点暴露无遗，如今已彻底绝迹。</li></ul><p>阵营五：行业最高法院</p><ol><li>MPEG (<code>.mpg</code> / <code>.mpeg</code>) —— 国际标准的鼻祖</li></ol><ul><li><strong>背景</strong>：ISO 国际标准组织（MPEG 组织）制定的初代官方格式。</li><li><strong>特点</strong>：它是 <strong>VCD（MPEG-1）</strong> 和 <strong>DVD（MPEG-2）</strong> 光盘时代的底层封装格式。</li><li><strong>现状</strong>：作为独立的文件后缀已经很少见，但它的家族后代（MPEG-4 也就是 MP4）正在统治世界。</li></ul><p><strong>常见编码格式</strong></p><p><strong>为什么要编码？</strong>：未经压缩的原始视频极其恐怖。一部 1080P、2小时的原始电影可能高达 <strong>几百GB 甚至上万GB</strong>，互联网根本无法传输。为了能让大家在网上流畅看视频，必须对画面进行极限压缩。</p><p><strong>H.264 ：它是一套极其经典的</strong>视频压缩算法标准<strong>（又称 AVC）。它能把巨无霸一样的原始视频，在</strong>保证肉眼看不出明显画质损失的前提下，压缩掉 90% 以上的体积。</p><p><strong>H.265 (HEVC)</strong>：H.264 的升级版。它的压缩效率翻倍，<strong>在同等画质下，体积只有 H.264 的一半</strong>。现在的 4K 高清电影、iPhone 的高效率录像基本都在使用它，缺点是对播放设备的 CPU 解码性能要求更高。</p><p><strong>AV1</strong>：一种全新的、开源免专利费的下一代编码格式，各大互联网巨头（谷歌、奈飞、微软等）正在全力推广，各大新显卡也已经全面支持硬件解码。</p><ol><li>H.261（1988年）—— 视频压缩的“开山鼻祖”</li></ol><ul><li><strong>诞生背景</strong>：当时互联网还没普及，大家通信靠的是昂贵且带宽极低的 ISDN 电话线。</li><li><strong>核心突破</strong>：它是人类历史上<strong>第一个流媒体视频压缩标准</strong>。它首次确立了现代视频压缩的核心框架：<strong>混合编码架构</strong>（将画面切成 $16 \times 16$ 的“宏块”，并引入了<strong>帧内预测</strong>和<strong>运动补偿</strong>）。直到今天的 H.265，依然在沿用这一套核心思想。</li><li><strong>应用场景</strong>：最早期的早期电视电话会议。它的分辨率低得可怜（只有 $352 \times 288$），但在当时能看到会动的真人画面，已经是科技奇迹了。</li></ul><ol><li>H.262（1994年）—— 广播电视与 DVD 的功臣</li></ol><ul><li><strong>诞生背景</strong>：单纯的电视会议标准满足不了人们对高质量电影、电视节目的渴望。于是 ITU-T 与 ISO 组织联手，将该技术同时命名为 <strong>MPEG-2 Part 2</strong>。</li><li><strong>核心突破</strong>：首次支持隔行扫描（Interlaced Video）和多声道音频，大幅度提升了高动态画面（如体育比赛）的清晰度。</li><li><strong>应用场景</strong>：统治了整个 <strong>DVD 光盘</strong>时代。直到今天，全球各国的<strong>无线数字电视、有线电视广播</strong>，底层依然有很大一部分在用 H.262/MPEG-2 进行传输。</li></ul><ol><li>H.263（1996年）—— 早期移动网络与视频通话的先驱</li></ol><ul><li><p><strong>背景与突破</strong>：专为低码率（低带宽）的网络环境深度优化。它改进了运动补偿算法，让视频在极烂、极卡的网络下，也能勉强保持连贯，不至于满屏马赛克。</p></li><li><p><strong>应用场景</strong>：它是 3G 时代手机视频通话（如 3GPP 规范）的绝对主力，也是早期 Flash 视频（FLV 格式）、早期的腾讯 QQ 视频聊天、</p><p>3GP 格式手机视频背后的压缩功臣。</p></li></ul><ol><li>H.264 / AVC（2003年）—— 统治世界的“格式之王”</li></ol><ul><li><strong>核心突破</strong>：技术跨代升级，同样命名为 <strong>MPEG-4 Part 10 (AVC)</strong>。相比 H.263，它的<strong>压缩效率直接翻倍</strong>。它引入了更精细的像素运动估计、多帧参考和更高级的熵编码。</li><li><strong>应用场景</strong>：<strong>它彻底开启了现代互联网视频时代。</strong> 如今你手机录的视频、B站/YouTube/抖音上的短视频、网络高清直播、蓝光光盘（Blu-ray），超过 80% 依然在用 H.264。它的硬件解码芯片已经做到了所有手机、电脑、电视的底层，播放它最省电、最流畅。</li></ul><ol><li>H.265 / HEVC（2013年）—— 超高清 4K/8K 的基石</li></ol><ul><li><strong>核心突破</strong>：同样命名为 <strong>MPEG-H Part 2 (HEVC)</strong>。面对 4K/8K 时代动辄数十 GB 的体积，H.265 将传统的 $16 \times 16$ 宏块升级为了最大 $64 \times 64$ 的 <strong>CTU（编码树单元）</strong>。</li><li><strong>惊人效果</strong>：<strong>在画质完全一样的前提下，体积比 H.264 再缩减 50%！</strong></li><li><strong>应用场景</strong>：现代智能手机（如 iPhone 的“高效格式”）、4K/8K 超高清电影、各大视频网站的“极清 4K”档位。</li></ul><blockquote><p>H.265 极其优秀，但因为它的<strong>专利授权费极其昂贵且混乱</strong>，导致谷歌、奈飞、亚马逊等互联网巨头非常不满。</p><p>于是，业界在 H.265 之后分化出了两条路：</p><ol><li><strong>官方正统续作</strong>：ITU 组织在 2020 年推出了 <strong>H.266 (VVC)</strong>，压缩率比 H.265 再提升 50%，专为未来 8K 和 VR 视频准备。</li><li><strong>开源免费联盟</strong>：巨头们联合搞出了 <strong>AV1</strong> 编码，完全免专利费，压缩率看齐甚至超越 H.265。目前 B站、YouTube、Netflix 正在疯狂把后台视频转码为 AV1。</li></ol></blockquote><p>网络协议将<strong>编码</strong>好的视频流，从主播端推送到服务器，在服务器上有个运行了同样协议的服务端来接收这些网络包，从而得到里面的视频流，这个过程称为<strong>接流</strong>。</p><p>服务端接到视频流之后，可以对视频流进行一定的处理，例如<strong>转码</strong>，也即从一个编码格式，转成另一种格式。因为观众使用的客户端千差万别，要保证他们都能看到直播。</p><p><strong>流处理</strong>完毕之后，就可以等待观众的客户端来请求这些视频流。观众的客户端请求的过程称为<strong>拉流</strong>。</p><p>如果有非常多的观众，同时看一个视频直播，那都从一个服务器上<strong>拉流</strong>，压力太大了，因而需要一个视频的<strong>分发</strong>网络，将视频预先加载到就近的边缘节点，这样大部分观众看的视频，是从边缘节点拉取的，就能降低服务器的压力。</p><p>所以整个直播流程,就可分为主播端采样编码推流,服务器接流处理分发，观众客户端拉流解码播放。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/uWh3/image-20260531151721013.png" alt="image-20260531151721013"></p><h2 id="编码流程"><a href="#编码流程" class="headerlink" title="编码流程"></a>编码流程</h2><p>视频序列分成三种帧。</p><ul><li><strong>I帧</strong>，也称关键帧。里面是完整的图片，只需要本帧数据，就可以完成解码。</li><li><strong>P帧</strong>，前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧（或P帧）的差别，解码时需要用之前缓存的画面，叠加上和本帧定义的差别，生成最终画面。</li><li><strong>B帧</strong>，双向预测内插编码帧。B帧记录的是本帧与前后帧的差别。要解码B帧，不仅要取得之前的缓存画面，还要解码之后的画面，通过前后画面的数据与本帧数据的叠加，取得最终的画面。</li></ul><p>I 帧（Intra-coded picture，帧内编码帧）</p><ul><li><strong>别名</strong>：关键帧（Keyframe）。</li><li><strong>特点</strong>：它是一张<strong>完整的、独立的图片</strong>，类似于一张高质量的 JPEG 照片。</li><li><strong>压缩方式</strong>：只进行“帧内压缩”（只去除一张图片内部的冗余信息），不依赖任何其他画面。</li><li><strong>作用</strong>：它是视频序列的“锚点”。当你在播放器里拖动进度条快进时，播放器必须先找到最近的 I 帧才能开始解码渲染。</li></ul><p>P 帧（Predictive-coded picture，前向预测编码帧）</p><ul><li><strong>别名</strong>：差别帧。</li><li><strong>特点</strong>：它不记录完整的图片，而<strong>只记录自己与前一个 I 帧（或前一个 P 帧）的“差异”和运动矢量</strong>。</li><li><strong>压缩方式</strong>：帧间压缩（向前参考）。</li><li><strong>作用</strong>：由于大多数视频相邻两帧的画面变化很小（比如只有人在动，背景没动），P 帧通过只存储变化的部分，数据量通常只有 I 帧的 <strong>1/2 到 1/4</strong>，大大节省了空间。</li></ul><p>B 帧（Bidirectionally predictive-coded picture，双向预测编码帧）</p><ul><li><strong>别名</strong>：双向参考帧。</li><li><strong>特点</strong>：它是压缩率最高的帧。它不仅参考<strong>前面</strong>已经出现的画面，还要参考<strong>后面</strong>即将出现的画面，通过两头对比，计算出自己的画面。</li><li><strong>压缩方式</strong>：帧间压缩（双向参考）。</li><li><strong>作用</strong>：B 帧只记录极少的运动变化信息，数据量极小，通常只有 I 帧的 <strong>1/10</strong> 左右。</li></ul><p>I帧最完整，B帧压缩率最高，而压缩后帧的序列，应该是在IBBP的间隔出现的。这就是<strong>通过时序进行编码</strong>。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/9ykE/image-20260531155913719.png" alt="image-20260531155913719"></p><p>在一帧中，分成多个片，每个片中分成多个宏块，每个宏块分成多个子块，这样将一张大的图分解成一个个小块，可以方便进行<strong>空间上的编码</strong>。尽管时空非常立体地组成了一个序列，但是总归还是要压缩成一个二进制流。这个流是有结构的，是一个个的<strong>网络提取层单元</strong>（<strong>NALU</strong>，<strong>Network Abstraction Layer Unit</strong>）。变成这种格式就是为了传输，因为网络上的传输，默认的是一个个的包，因而这里也就分成了一个个的单元。</p><p>每一个NALU首先是一个起始标识符，用于标识NALU之间的间隔；然后是NALU的头，里面主要配置了NALU的类型；最终Payload里面是NALU承载的数据。</p><p>在NALU头里面，主要的内容是类型<strong>NAL Type</strong>。</p><ul><li>0x07表示SPS，是序列参数集， 包括一个图像序列的所有信息，如图像尺寸、视频格式等。</li><li>0x08表示PPS，是图像参数集，包括一个图像的所有分片的所有相关信息，包括图像类型、序列号等</li></ul><p><img data-src="https://files.seeusercontent.com/2026/05/31/Ep2x/image-20260531155954397.png" alt="image-20260531155954397"></p><p>在传输视频流之前，必须要传输这两类参数，不然无法解码。为了保证容错性，每一个I帧前面，都会传一遍这两个参数集合。</p><p>如果NALU Header里面的表示类型是SPS或者PPS，则Payload中就是真正的参数集的内容。</p><p>如果类型是帧，则Payload中才是正的视频数据，当然也是一帧一帧存放的，前面说了，一帧的内容还是挺多的，因而每一个NALU里面保存的是一片。对于每一片，到底是I帧，还是P帧，还是B帧，在片结构里面也有个Header，这里面有个类型，然后是片的内容。</p><p>这样，整个格式就出来了，<strong>一个视频，可以拆分成一系列的帧，每一帧拆分成一系列的片，每一片都放在一个NALU里面，NALU之间都是通过特殊的起始标识符分隔，在每一个I帧的第一片前面，要插入单独保存SPS和PPS的NALU，最终形成一个长长的NALU序列</strong>。</p><h2 id="推流流程"><a href="#推流流程" class="headerlink" title="推流流程"></a>推流流程</h2><p>使用RTMP协议推送网络包。RTMP是基于TCP的，因而肯定需要双方建立一个TCP的连接。在有TCP的连接的基础上，还需要建立一个RTMP的连接，也即在程序里面，你需要调用RTMP类库的Connect函数，显示创建一个连接。</p><p>RTMP也需要先简历连接,协商本版号和时间戳等信息。</p><p>客户端发送C0、C1、 C2，服务器发送S0、 S1、 S2。</p><p>首先，客户端发送C0表示自己的版本号，不必等对方的回复，然后发送C1表示自己的时间戳。</p><p>服务器只有在收到C0的时候，才能返回S0，表明自己的版本号，如果版本不匹配，可以断开连接。</p><p>服务器发送完S0后，也不用等什么，就直接发送自己的时间戳S1。客户端收到S1的时候，发一个知道了对方时间戳的ACK C2。同理服务器收到C1的时候，发一个知道了对方时间戳的ACK S2。于是，握手完成。</p><p>握手之后，双方需要互相传递一些控制信息，例如Chunk块的大小、窗口大小等。</p><p>真正传输数据的时候，还是需要创建一个流Stream，然后通过这个Stream来推流publish。推流的过程，就是将NALU放在Message里面发送，这个也称为<strong>RTMP Packet包</strong>。Message的格式就像这样。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/p6iK/image-20260531180233027.png" alt="image-20260531180233027"></p><p>发送的时候，去掉NALU的起始标识符。RTMP在收发数据的时候并不是以Message为单位的，而是把Message拆分成Chunk发送，而且必须在一个Chunk发送完成之后，才能开始发送下一个Chunk。每个Chunk中都带有Message ID，表示属于哪个Message，接收端也会按照这个ID将Chunk组装成Message。</p><p>连接的时候，设置的Chunk块大小就是指这个Chunk。将大的消息变为小的块再发送，可以在低带宽的情况下，减少网络拥塞。</p><p>假设一个视频的消息长度为307，但是Chunk大小约定为128，于是会拆分为三个Chunk。</p><p>第一个Chunk的Type＝0，表示Chunk头是完整的；头里面Timestamp为1000，总长度Length 为307，类型为9，是个视频，Stream ID为12346，正文部分承担128个字节的Data。</p><p>第二个Chunk也要发送128个字节，Chunk头由于和第一个Chunk一样，因此采用Chunk Type＝3，表示头一样就不再发送了。</p><p>第三个Chunk要发送的Data的长度为307-128-128=51个字节，还是采用Type＝3。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/aZy1/image-20260531180615089.png" alt="image-20260531180615089"></p><h2 id="拉流流程"><a href="#拉流流程" class="headerlink" title="拉流流程"></a>拉流流程</h2><p>看直播的观众就可以通过RTMP协议从流媒体服务器上拉取，但是这么多的用户量，都去同一个地方拉取，服务器压力会很大，而且用户分布在全国甚至全球，如果都去统一的一个地方下载，也会时延比较长，需要有分发网络。</p><p>分发网络分为<strong>中心</strong>和<strong>边缘</strong>两层。边缘层服务器部署在全国各地及横跨各大运营商里，和用户距离很近。中心层是流媒体服务集群，负责内容的转发。智能负载均衡系统，根据用户的地理位置信息，就近选择边缘服务器，为用户提供推/拉流服务。中心层也负责转码服务，例如，把RTMP协议的码流转换为HLS码流。</p><p>类似地,客户端拉流被，客户端通过RTMP协议拉取，然后组合为NALU，解码成视频格式进行播放。</p><h1 id="P2P协议"><a href="#P2P协议" class="headerlink" title="P2P协议"></a>P2P协议</h1><p>无论是HTTP的方式，还是FTP的方式，都有一个比较大的缺点，就是<strong>难以解决单一服务器的带宽压力</strong>， 因为它们使用的都是传统的客户端服务器的方式。</p><p>后来，一种创新的、称为P2P的方式流行起来。<strong>P2P</strong>就是<strong>peer-to-peer</strong>。资源开始并不集中地存储在某些设备上，而是分散地存储在多台设备上。这些设备我们姑且称为peer。</p><p>想要下载一个文件的时候，你只要得到那些已经存在了文件的peer，并和这些peer之间，建立点对点的连接，而不需要到中心服务器上，就可以就近下载文件。一旦下载了文件，你也就成为peer中的一员，你旁边的那些机器，也可能会选择从你这里下载文件，所以当你使用P2P软件的时候，例如BitTorrent，往往能够看到，既有下载流量，也有上传的流量，也即你自己也加入了这个P2P的网络，自己从别人那里下载，同时也提供给其他人下载。可以想象，这种方式，参与的人越多，下载速度越快。</p><p>通过<code>.torrent</code>文件,也就是种子,知道文件信息。<code>.torrent</code>文件由两部分组成，分别是：<strong>announce</strong>（<strong>tracker URL</strong>）和<strong>文件信息</strong>。</p><p>文件信息里面有这些内容。</p><ul><li><strong>info区</strong>：这里指定的是该种子有几个文件、文件有多长、目录结构，以及目录和文件的名字。</li><li><strong>Name字段</strong>：指定顶层目录名字。</li><li><strong>每个段的大小</strong>：BitTorrent（简称BT）协议把一个文件分成很多个小段，然后分段下载。</li><li><strong>段哈希值</strong>：将整个种子中，每个段的SHA-1哈希值拼在一起。</li></ul><p>下载时，BT客户端首先解析.torrent文件，得到tracker地址，然后连接tracker服务器。tracker服务器回应下载者的请求，将其他下载者（包括发布者）的IP提供给下载者。下载者再连接其他下载者，根据<code>.torrent</code>文件，两者分别对方告知自己已经有的块，然后交换对方没有的数据。此时不需要其他服务器参与，并分散了单个线路上的数据流量，因此减轻了服务器的负担。</p><p>下载者每得到一个块，需要算出下载块的Hash验证码，并与.torrent文件中的对比。如果一样，则说明块正确，不一样则需要重新下载这个块。这种规定是为了解决下载内容的准确性问题。</p><p>这种方式特别依赖tracker。tracker需要收集下载者信息的服务器，并将此信息提供给其他下载者，使下载者们相互连接起来，传输数据。虽然下载的过程是非中心化的，但是加入这个P2P网络的时候，都需要借助tracker中心服务器，这个服务器是用来登记有哪些用户在请求哪些资源。</p><p>所以，这种工作方式有一个弊端，一旦tracker服务器出现故障或者线路遭到屏蔽，BT工具就无法正常工作了。</p><h3 id="DHT去中心化网络"><a href="#DHT去中心化网络" class="headerlink" title="DHT去中心化网络"></a>DHT去中心化网络</h3><p>后来就有了一种叫作<strong>DHT</strong>（<strong>Distributed Hash Table</strong>）的去中心化网络。每个加入这个DHT网络的人，都要<strong>负责存储这个网络里的资源信息和其他成员的联系信息</strong>，相当于所有人一起构成了一个庞大的分布式存储数据库。</p><p>有一种著名的DHT协议，叫<strong>Kademlia协议</strong>。</p><p>任何一个BitTorrent启动之后，它都有两个角色。一个是<strong>peer</strong>，监听一个TCP端口，用来上传和下载文件，这个角色表明，我这里有某个文件。另一个角色<strong>DHT node</strong>，监听一个UDP的端口，通过这个角色，这个节点加入了一个DHT的网络。在DHT网络里面，每一个DHT node都有一个ID。这个ID是一个很长的串。每个DHT node都有责任掌握一些知识，也就是<strong>文件索引</strong>，也即它应该知道某些文件是保存在哪些节点上。它只需要有这些知识就可以了，而它自己本身不一定就是保存这个文件的节点。</p><h2 id="数据中心"><a href="#数据中心" class="headerlink" title="数据中心"></a>数据中心</h2><p>数据中心里面是服务器。服务器被放在一个个叫作<strong>机架</strong>（<strong>Rack</strong>）的架子上面。数据中心的入口和出口也是路由器，由于在数据中心的边界，就像在一个国家的边境，称为<strong>边界路由器</strong>（<strong>Border Router</strong>）。为了高可用，边界路由器会有多个。</p><p>一般家里只会连接一个运营商的网络，而为了高可用，为了当一个运营商出问题的时候，还可以通过另外一个运营商来提供服务，所以数据中心的边界路由器会连接多个运营商网络。</p><p>数据中心里面往往有非常多的机器，当塞满一机架的时候，需要有交换机将这些服务器连接起来，可以互相通信。这些交换机往往是放在机架顶端的，所以经常称为<strong>TOR</strong>（<strong>Top Of</strong> <strong>Rack</strong>）<strong>交换机</strong>。</p><p>当一个机架放不下的时候，就需要多个机架，还需要有交换机将多个机架连接在一起。这些交换机对性能的要求更高，带宽也更大。这些交换机称为<strong>汇聚层交换机</strong>（<strong>Aggregation Layer</strong>）。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/W4br/image-20260531211311654.png" alt="image-20260531211311654"></p><p>数据中心里面的每一个连接都是需要考虑高可用的。这里首先要考虑的是，如果一台机器只有一个网卡，上面连着一个网线，接入到TOR交换机上。如果网卡坏了，或者不小心网线掉了，机器就上不去了。所以，需要至少两个网卡、两个网线插到TOR交换机上，但是两个网卡要工作得像一张网卡一样，这就是常说的<strong>网卡绑定</strong>（<strong>bond</strong>）。</p><p>这就需要服务器和交换机都支持一种协议<strong>LACP</strong>（<strong>Link Aggregation Control Protocol</strong>）。它们互相通信，将多个网卡聚合称为一个网卡，多个网线聚合成一个网线，在网线之间可以进行负载均衡，也可以为了高可用作准备。</p><p>网卡有了高可用保证，但交换机还有问题。如果一个机架只有一个交换机，它挂了，那整个机架都不能上网了。因而TOR交换机也需要高可用，同理接入层和汇聚层的连接也需要高可用性，也不能单线连着</p><p>最传统的方法是，部署两个接入交换机、两个汇聚交换机。服务器和两个接入交换机都连接，接入交换机和两个汇聚都连接，当然这样会形成环，所以需要启用STP协议，去除环，但是这样两个汇聚就只能一主一备了。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/5yeI/image-20260531211434589.png" alt="image-20260531211434589"></p><p>另一种方法是，将多个交换机形成一个逻辑的交换机，服务器通过多根线分配连到多个接入层交换机上，而接入层交换机多根线分别连接到多个交换机上，并且通过堆叠的私有协议，形成<strong>双活</strong>的连接方式。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/s8Zt/image-20260531211452050.png" alt="image-20260531211452050" style="zoom:80%;" /></p><p>汇聚层将大量的计算节点相互连接在一起，形成一个集群。在这个集群里面，服务器之间通过二层互通，这个区域常称为一个<strong>POD</strong>（<strong>Point Of Delivery</strong>），有时候也称为一个<strong>可用区</strong>（<strong>Available Zon</strong>e）。</p><p>当节点数目再多的时候，一个可用区放不下，需要将多个可用区连在一起，连接多个可用区的交换机称为<strong>核心交换机</strong>。</p><h1 id="VPN"><a href="#VPN" class="headerlink" title="VPN"></a>VPN</h1><p>有的公司有多个数据中心，需要将多个数据中心连接起来，或者需要办公室和数据中心连接起来。这该怎么办呢？</p><ul><li>第一种方式是走公网，但是公网太不安全，你的隐私可能会被别人偷窥。</li><li>第二种方式是租用专线的方式把它们连起来，这是土豪的做法，需要花很多钱。</li><li>第三种方式是用VPN来连接，这种方法比较折中，安全又不贵。</li></ul><p><strong>VPN</strong>，全名<strong>Virtual Private Network</strong>，<strong>虚拟专用网</strong>，就是利用开放的公众网络，建立专用数据传输通道，将远程的分支机构、移动办公人员等连接起来。</p><p><img data-src="https://files.seeusercontent.com/2026/05/31/Usr3/image-20260531212927889.png" alt="image-20260531212927889"></p><h2 id="VPN工作方式"><a href="#VPN工作方式" class="headerlink" title="VPN工作方式"></a>VPN工作方式</h2><p>VPN通过隧道技术在公众网络上仿真一条点到点的专线，是通过利用一种协议来传输另外一种协议的技术，这里面涉及三种协议：<strong>乘客协议</strong>、<strong>隧道协议</strong>和<strong>承载协议</strong>。</p><p><strong>IPsec VPN</strong>。这是基于IP协议的<strong>安全隧道协议</strong>，为了保证在公网上面信息的安全，因而采取了一定的机制保证安全性。</p><ul><li>机制一：<strong>私密性</strong>，防止信息泄露给未经授权的个人，通过加密把数据从明文变成无法读懂的密文，从而确保数据的私密性。 前面讲HTTPS的时候，说过加密可以分为对称加密和非对称加密。对称加密速度快一些。而VPN一旦建立，需要传输大量数据，因而我们采取对称加密。但是同样，对称加密还是存在加密密钥如何传输的问题，这里需要用到因特网密钥交换（IKE，Internet Key Exchange）协议。</li><li>机制二：<strong>完整性</strong>，数据没有被非法篡改，通过对数据进行hash运算，产生类似于指纹的数据摘要，以保证数据的完整性。</li><li>机制三：<strong>真实性</strong>，数据确实是由特定的对端发出，通过身份认证可以保证数据的真实性。</li></ul><p><img data-src="https://files.seeusercontent.com/2026/05/31/oZ6a/image-20260531213609789.png" alt="image-20260531213609789"></p><p>有了IPsec VPN之后，客户端发送的明文的IP包，都会被加上ESP头和IP头，在公网上传输，由于加密，可以保证不被窃取，到了对端后，去掉ESP的头，进行解密。</p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/05/27/%E7%BD%91%E7%BB%9C%E4%B8%AD%E6%9C%89%E8%B6%A3%E7%9A%84%E5%8D%8F%E8%AE%AE/</id>
    <link href="https://www.sekyoro.top/2026/05/27/%E7%BD%91%E7%BB%9C%E4%B8%AD%E6%9C%89%E8%B6%A3%E7%9A%84%E5%8D%8F%E8%AE%AE/"/>
    <published>2026-05-27T10:28:35.000Z</published>
    <summary>
      <![CDATA[<p>介绍一些网络协议,主要内容来自极客时间趣谈网络协议.<br>]]>
    </summary>
    <title>网络中有趣的协议</title>
    <updated>2026-06-04T14:12:06.696Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="AI Coding" scheme="https://www.sekyoro.top/tags/AI-Coding/"/>
    <category term="Harness" scheme="https://www.sekyoro.top/tags/Harness/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>自AI Coding或者说Vibe Coding流行起来,开发者也意识到如何利用以及约束AI以写出更好维护、扩展的生产级代码越来越重要。Anthropic提出<a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">Harness工程</a>以长时间使用Agents生产代码。本篇文章主要介绍一下Harness工程以及相关实践。</p><span id="more"></span><p>自Transformers为基底的ChatGPT流行起来,以自然语言作为主要沟通的方式与模型进行对话,同时,包括图像、音视频的多模态、跨模态模型也越来越多,用于不同任务的模型也越来越多。在编码任务中,Claude Sonnet、Opus,GPT,GLM,DeepSeek,Kimi,Gemini等都发挥出色<a href="https://www.swebench.com/">SWE-bench Leaderboards</a>。</p><p>随着基底模型发展的越来越好,GPT,Gemini,Claude,MiniMax等顶级模型能力不分伯仲,因此更需要考虑如何更好的通过某种规范和约定更便捷高效地进行AI Coding,产出高质量代码。</p><h1 id="Harness的提出"><a href="#Harness的提出" class="headerlink" title="Harness的提出"></a>Harness的提出</h1><p>当前AI Coding都是通过Agent的形式解决任务,”Agent“会选择上下文、使用相应工具等方式连接开发者与模型并创建文件、执行指令以及输出对应代码。</p><p>但问题也依然存在：</p><p>长时间运行 Agent 的核心挑战在于，它们必须以离散的会话（session）方式工作，而每个新会话开始时都没有此前的记忆。想象一个软件项目由轮班工程师负责，每位新到岗的工程师对上一班发生了什么毫无记忆。<strong>由于上下文窗口有限，且大多数复杂项目无法在单个窗口内完成</strong>，Agent 需要一种方式来衔接不同的编码会话。</p><p>而Anthropic提出的解决方案是：提出一套双管齐下的方案，使 Claude Agent SDK 能够在多个上下文窗口之间高效工作：一个用于首次运行时搭建环境的<strong>初始化 Agent（initializer agent）</strong>，以及一个在每次会话中负责推进增量进展、同时为下一次会话留下清晰产物的<strong>编码 Agent（coding agent）</strong>。</p><p>在长时间运行Agent情况下,由于每次对话会包含上下文等信息,过长的上下文会降低模型思考能力。一般的Agent都具备压缩能力，但即使这种情况下，通过短暂的一句话也很难构建一个完整的项目。</p><p>Anthropic将问题分解为两个部分。首先，需要搭建一个初始环境，为给定提示词<strong>所要求的所有功能奠定基础</strong>，<strong>引导 Agent 逐步骤、逐功能地推进工作</strong>。其次，应该<strong>提示每个 Agent 朝目标做出增量进展，同时在会话结束时将环境保持在一个干净的状态</strong>。</p><ol><li><strong>初始化 Agent（Initializer agent）</strong>：第一个 Agent 会话使用专门的提示词，要求模型搭建初始环境：一个 <code>init.sh</code> 脚本、一个用于记录各 Agent 工作日志的 <code>claude-progress.txt</code> 文件，以及一个展示新增文件的初始 git commit。</li><li><strong>编码 Agent（Coding agent）</strong>：后续每个会话要求模型做出增量进展，然后留下结构化的更新记录。</li></ol><h2 id="上下文环境组成"><a href="#上下文环境组成" class="headerlink" title="上下文环境组成"></a>上下文环境组成</h2><p><strong>Feature List</strong></p><p>为了解决 Agent 试图一次性完成整个应用、或过早认为项目已完成的问题，初始化 Agent 编写一份详尽的功能需求文件，对用户的初始提示词进行扩展.</p><p>也就是通过一个文件记录需要实现哪些功能并标记功能完成状态。</p><p><strong>Increment Progress</strong></p><p>相比于一次完成所有任务,下一轮迭代的编码 Agent 被要求每次只处理一个功能。这种增量式方法对于解决 Agent 一次做太多事情的倾向至关重要。解决方式是求模型将进度提交到 git 并附带描述性的 commit 信息，同时在进度文件中写下工作总结。<strong>这使得模型可以利用 git 回滚错误的代码变更，恢复到代码库的正常状态。</strong></p><p><strong>Testing</strong></p><p>一种常见的失败情况是:<strong>Claude 倾向于在没有充分测试的情况下就将功能标记为已完成</strong>。在没有明确提示的情况下，Claude 通常会修改代码，甚至用单元测试或 <code>curl</code> 命令对开发服务器做测试，但无法识别该功能在端到端场景下其实并不可用。</p><p>我个人遇到的问题就是,在前端项目中,视觉看到的一些问题让Agent处理,其往往只是通过代码筛查。解决方式是:明确提示使用浏览器自动化工具并像真实用户一样进行全部测试.</p><h2 id="如何工作"><a href="#如何工作" class="headerlink" title="如何工作"></a>如何工作</h2><p>1.运行 <code>pwd</code> 查看当前工作目录。只能编辑该目录下的文件。</p><p>2.阅读 git 日志和进度文件，<strong>了解近期的工作内容</strong>。</p><p>3.阅读功能清单文件，选择优先级最高且尚未完成的功能开始工作。</p><p>此外，<strong>让初始化 Agent 编写一个 <code>init.sh</code> 脚本来启动开发服务器</strong>，并在实现新功能前先跑一遍基本的端到端测试，也很有帮助。</p><div class="table-container"><table><thead><tr><th>问题</th><th>初始化 Agent 的应对</th><th>编码 Agent 的应对</th></tr></thead><tbody><tr><td>Claude 过早宣布整个项目完成。</td><td>建立功能清单文件：基于输入规格，创建一个结构化的 JSON 文件，列出所有端到端的功能描述。</td><td>在每次会话开始时读取功能清单文件，选择一个功能开始工作。</td></tr><tr><td>Claude 在退出时留下了 bug 或未记录的进展。</td><td>初始化一个 git 仓库并创建进度记录文件。</td><td>会话开始时读取进度记录文件和 git 提交日志，并在开发服务器上运行基本测试以捕获未记录的 bug。会话结束时提交一次 git commit 并更新进度记录。</td></tr><tr><td>Claude 过早将功能标记为已完成。</td><td>建立功能清单文件。</td><td>自行验证所有功能。只有经过仔细测试后，才将功能标记为”通过”。</td></tr><tr><td>Claude 需要花时间摸索如何运行应用。</td><td>编写一个 <code>init.sh</code> 脚本来启动开发服务器。</td><td>会话开始时先读取 <code>init.sh</code>。</td></tr></tbody></table></div><h2 id="Harness-Engineering"><a href="#Harness-Engineering" class="headerlink" title="Harness Engineering"></a>Harness Engineering</h2><blockquote><p><strong>Harness Engineering（驾驭工程）</strong>是围绕 AI 智能体设计和构建<strong>约束机制、反馈回路、工作流控制和持续改进循环</strong>的系统工程实践。</p><p>它不优化模型本身，而是优化模型运行的环境。核心哲学八个字：<strong>人类掌舵，智能体执行（Human Steer, Agent Execute）</strong>。Harness一词来自马具——缰绳、马鞍、嚼子——这是一套引导强大但不可预测的动物的完整装备。<strong>驾驭工程不是去削弱 AI 的能力，而是为它打造一套黄金缰绳，让它跑得又快又稳。</strong></p></blockquote><p>Harness其实也是基于AI模型的应用,可以说从一开始的prompt enginnering到上下文工程(注入相关信息)再到现在的Harness Engineering.</p><div class="table-container"><table><thead><tr><th style="text-align:left">范式</th><th style="text-align:left">核心问题</th><th style="text-align:left">优化对象</th><th style="text-align:left">交互模式</th></tr></thead><tbody><tr><td style="text-align:left"><strong>提示词工程</strong></td><td style="text-align:left">怎么把话说清楚</td><td style="text-align:left">Prompt 的措辞、格式、示例</td><td style="text-align:left">一问一答</td></tr><tr><td style="text-align:left"><strong>上下文工程</strong></td><td style="text-align:left">怎么给 AI 喂信息</td><td style="text-align:left">文档、代码片段、历史对话</td><td style="text-align:left">信息注入 → 生成</td></tr><tr><td style="text-align:left"><strong>驾驭工程</strong></td><td style="text-align:left">怎么让 Agent 可靠工作</td><td style="text-align:left">约束、反馈回路、控制系统</td><td style="text-align:left">人类掌舵，Agent 执行</td></tr></tbody></table></div><p>个人认为,这个词的出现一方面还是Anthropic的工程师包括CEO等人提出,有影响力,另一方面也是对AI工程化的系统性总结.而且目前看来对于开发者编码提效很有用.</p><p>目前针对Agent Coding常见的三个问题,归类为:</p><p><strong>失败模式 1：试图一步到位（One-shotting）</strong></p><p>Agent 倾向于在一个会话里把所有功能都做完。结果是上下文窗口耗尽，留下一堆没有文档的半成品代码，下一个会话启动时只能花大量时间猜测之前发生了什么。</p><p><strong>失败模式 2：过早宣布胜利</strong></p><p>在项目后期，当部分功能已经完成后，Agent 会环顾四周，看到已有进展就直接宣布任务完成——即使还有大量功能未实现。</p><p><strong>失败模式 3：过早标记功能完成</strong></p><p>在没有明确提示的情况下，Agent 写完代码就标记为完成，却没有做端到端测试。单元测试或 curl 命令通过了不代表功能真正可用。</p><p>此外，智能体还有一个危险特性：<strong>它非常擅长模式复制</strong>。代码库里有什么模式，它就忠实地复制并放大——包括坏模式和架构漂移。这意味着不加约束的 Agent 会以惊人的速度积累技术债务。</p><p>解决方案总结为:</p><ol><li><p>上下文工程,提供一个稳定、小巧的入口点，然后教 Agent 根据当前任务按需检索和拉取更多的上下文。</p></li><li><p>架构约束。</p></li><li>反馈循环。</li><li>熵管理。</li></ol><h1 id="Harness的实践"><a href="#Harness的实践" class="headerlink" title="Harness的实践"></a>Harness的实践</h1><p>目前许多AI IDE和CLI都自带一些良好的harness实践,比如记录改动和执行测试等等。</p><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><h3 id="Web项目实战路径"><a href="#Web项目实战路径" class="headerlink" title="Web项目实战路径"></a>Web项目实战路径</h3><p>不要在初始阶段让 <code>Codex</code>、<code>Claude Code</code> 或 <code>OpenCode</code> 从零生成大型项目；应先限定范围、输入约束和验收标准，以降低后续维护成本。</p><p>写PRD(项目需求说明)-&gt;让Agent只读分析-&gt;小步实现-&gt;验证(跑单测、看diff等)</p><blockquote><p>不要让 Agent 一次性实现完整 CRUD。每完成一步都应能够检查 diff 并运行验证</p></blockquote><p><img data-src="https://files.seeusercontent.com/2026/05/26/zo6L/image-20260526181545394.png" alt="image-20260526181545394"></p><p>重点:</p><ul><li>PRD 是否清楚。</li><li>Agent 是否先读代码。</li><li>计划是否可审。</li><li>验证是否闭环。</li><li>错误是否沉淀到规范。</li></ul><h3 id="Bug修复与重构路径"><a href="#Bug修复与重构路径" class="headerlink" title="Bug修复与重构路径"></a>Bug修复与重构路径</h3><p>先收集完整证据：</p><ul><li>错误现象。</li><li>复现步骤。</li><li>日志或 stack trace。</li><li>最近相关改动。</li><li>期望行为。</li></ul><p>如果缺少复现步骤，先让 Agent 做只读分析和复现计划。不要让它直接改代码。</p><figure class="highlight markdown"><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="bullet">1.</span> 先复现或解释如何复现。</span><br><span class="line"><span class="bullet">2.</span> 提出 2-3 个可能根因。</span><br><span class="line"><span class="bullet">3.</span> 用代码和日志验证假设。</span><br><span class="line"><span class="bullet">4.</span> 做最小修复。</span><br><span class="line"><span class="bullet">5.</span> 补回归测试。</span><br><span class="line"><span class="bullet">6.</span> 跑验证。</span><br><span class="line"><span class="bullet">7.</span> 写复盘。</span><br></pre></td></tr></table></figure><p>这些做法看起来省时间，通常会增加下一轮返工风险：</p><ul><li>不复现直接改。</li><li>猜一个原因就改。</li><li>修完不补测试。</li><li>改大范围无关代码。</li></ul><p>还要避免把“顺手重构”和 bugfix 混在一起。bugfix 先追求最小修复，结构调整另开任务。</p><p><strong>重构路径</strong></p><p>重构前先把这几个问题问完：</p><ul><li>外部行为是否保持不变？</li><li>现有测试是否足够？</li><li>是否需要 characterization test？</li><li>重构范围是否清楚？</li><li>是否有回滚点？</li></ul><p><strong>Agent流程</strong></p><figure class="highlight markdown"><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="bullet">1.</span> 分析当前结构。</span><br><span class="line"><span class="bullet">2.</span> 找重复、复杂度、边界问题。</span><br><span class="line"><span class="bullet">3.</span> 写重构计划。</span><br><span class="line"><span class="bullet">4.</span> 人审计划。</span><br><span class="line"><span class="bullet">5.</span> 小步改动。</span><br><span class="line"><span class="bullet">6.</span> 每步跑测试。</span><br><span class="line"><span class="bullet">7.</span> 最后做 diff 审查。</span><br></pre></td></tr></table></figure><p>重构时可以让 Agent 先补 characterization test。它们不证明现有行为正确，只负责把现有行为固定下来，避免重构把行为改掉。</p><p><strong>验证标准</strong></p><p>Bug 修复：</p><ul><li>原 bug 可复现。</li><li>修复后不再复现。</li><li>有回归测试。</li></ul><p>重构：</p><ul><li>外部行为不变。</li><li>测试通过。</li><li>diff 中没有无关功能变化。</li></ul><p><strong>停止条件</strong></p><p>遇到下面情况，先停：</p><ul><li>bug 无法复现，也无法给出可信复现路径。</li><li>修复需要改动权限、支付、数据删除、认证等高风险区域。</li><li>重构范围越过原计划。</li><li>测试失败原因说不清。</li><li>Agent 要引入新依赖绕过问题。</li></ul><p>停止不是失败。先把状态、证据和未解决问题写进任务记录，再决定是否拆分任务。</p><p><strong>复盘沉淀</strong></p><p>任务结束后，让 Agent 回答这些问题，再决定要不要沉淀到规范：</p><figure class="highlight dos"><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></pre></td><td class="code"><pre><span class="line">这个 bug 为什么会出现？</span><br><span class="line">哪些测试原本应该发现它？</span><br><span class="line">AGENTS.<span class="built_in">md</span> / CLAUDE.<span class="built_in">md</span> 需要新增什么规则？</span><br><span class="line">是否应该做成 bug-fix skill？</span><br></pre></td></tr></table></figure><h3 id="自动化脚本实战路径"><a href="#自动化脚本实战路径" class="headerlink" title="自动化脚本实战路径"></a>自动化脚本实战路径</h3><p>一个常见的脚本执行步骤,定义CLI-&gt;让Agent写计划-&gt;实现和测试-&gt;整理交付记录</p><p><strong>任务 1：定义 CLI</strong></p><figure class="highlight smali"><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><br><span class="line"> <span class="built_in"> check-md-links </span>&lt;dir&gt;</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">  0 表示无损坏链接。</span><br><span class="line">  1 表示存在损坏链接。</span><br></pre></td></tr></table></figure><p><strong>任务 2：让 Agent 写计划</strong></p><p>先把 Agent 限制在计划阶段：</p><ul><li>不直接实现。</li><li>先列边界情况。</li><li>先列测试样例。</li></ul><p>边界情况：</p><ul><li>空目录。</li><li>无链接文件。</li><li>相对路径。</li><li>锚点链接。</li><li>外部链接跳过或只记录。</li></ul><p><strong>任务 3：实现和测试</strong></p><p>顺序：</p><p><img data-src="https://files.seeusercontent.com/2026/05/26/6jIl/image-20260526175700277.png" alt="image-20260526175700277"></p><p><strong>任务 4：整理交付记录</strong></p><p>让 Agent 在完成后输出：</p><figure class="highlight nestedtext"><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></pre></td><td class="code"><pre><span class="line"><span class="attribute">Changed files</span><span class="punctuation">:</span></span><br><span class="line"><span class="attribute">Commands run</span><span class="punctuation">:</span></span><br><span class="line"><span class="attribute">Known limitations</span><span class="punctuation">:</span></span><br><span class="line"><span class="attribute">Examples</span><span class="punctuation">:</span></span><br><span class="line"><span class="attribute">Next safe improvement</span><span class="punctuation">:</span></span><br></pre></td></tr></table></figure><p>如果它只说“完成了”，说明练习还没结束。</p><p><strong>验证标准</strong></p><ul><li>样例目录下能识别坏链接。</li><li>正常链接不误报。</li><li>输出包含文件和行号。</li><li>测试通过。</li><li>退出码符合约定。</li><li>已说明暂不支持的链接类型。</li></ul><h3 id="工作流工具实战路径"><a href="#工作流工具实战路径" class="headerlink" title="工作流工具实战路径"></a>工作流工具实战路径</h3><ol><li>手动创建一个任务目录。</li><li>让 Agent 根据需求填写 PRD。</li><li>让 Agent 先写实现计划，不写代码。</li><li>人工审查 Agent 的实现计划。</li><li>让 Agent 执行。</li><li>让 Agent 把验证结果记录到 <code>status.md</code>。</li><li>任务结束后写 <code>retrospective.md</code>。</li></ol><h3 id="个人开发者工作流"><a href="#个人开发者工作流" class="headerlink" title="个人开发者工作流"></a>个人开发者工作流</h3><p>我认为个人开发者vibe coding最常见的问题就是基本只通过简单的一段话就想实现完整的某个功能/修改一个可能涉及范围比较大的bug,造成的后果可能是功能实现不完善或者bug无法完全被修复</p><p><img data-src="https://files.seeusercontent.com/2026/05/26/gj0S/image-20260526181708585.png" alt="image-20260526181708585"></p><p>任务前:</p><p>任务开始前先准备这五件事：</p><ul><li>目标。</li><li>不做什么。</li><li>相关路径。</li><li>验收标准。</li><li>验证命令。</li></ul><p>任务中:</p><p>任务进行中关注这些边界：</p><ul><li>单次只做一个目标。</li><li>先计划再实现。</li><li>大任务拆成小任务。</li><li>改动范围不清楚就暂停。</li><li>测试失败先定位再修。</li></ul><p>任务后:</p><p>任务收尾时，不要只结束会话；应把可复用经验记录下来：</p><ul><li>哪条规则应该沉淀？</li><li>哪个流程适合做成 skill？</li><li>哪个测试应该补？</li><li>哪个文档应该更新？</li></ul><h3 id="团队工作流"><a href="#团队工作流" class="headerlink" title="团队工作流"></a>团队工作流</h3><p>团队使用 AI 的重点不在每个人都擅长 prompt，而在团队共用同一套上下文和质量标准。</p><p><strong>团队规范</strong></p><p><img data-src="https://files.seeusercontent.com/2026/05/26/1Aok/image-20260526183226893.png" alt="image-20260526183226893"></p><p>团队流程</p><p><img data-src="https://files.seeusercontent.com/2026/05/26/qCt1/image-20260526183247644.png" alt="image-20260526183247644"></p><p>可以追踪这些信号：</p><ul><li>AI 任务一次通过率。</li><li>审查发现的高危问题数。</li><li>CI 修复轮次。</li><li>重复 bug 数量。</li><li>需求澄清轮次。</li><li>从 issue 到 PR 的周期。</li></ul><p>指标用于改进工作流。指标异常时，优先检查任务定义、上下文入口和验证闸门，再考虑是否换模型。</p><h3 id="检查清单"><a href="#检查清单" class="headerlink" title="检查清单"></a>检查清单</h3><p><strong>开发前清单</strong></p><ul><li>[ ] 目标清楚。</li><li>[ ] 不做哪些事已经写清楚。</li><li>[ ] 上下文入口清楚。</li><li>[ ] 相关规范文件存在。</li><li>[ ] 验收标准可验证。</li><li>[ ] 高风险点已标注。</li><li>[ ] 需要人确认的决策已列出。</li></ul><p><strong>让 Agent 开始前</strong></p><ul><li>[ ] 要求先只读分析。</li><li>[ ] 要求列文件范围。</li><li>[ ] 要求列计划。</li><li>[ ] 要求列验证命令。</li><li>[ ] 明确不要改无关文件。</li></ul><p><strong>实现中清单</strong></p><ul><li>[ ] 改动是否保持最小。</li><li>[ ] 是否遵守现有模式。</li><li>[ ] 是否引入新依赖。</li><li>[ ] 是否修改了公共接口。</li><li>[ ] 是否同步更新类型、文档、测试。</li></ul><p><strong>完成前清单</strong></p><ul><li>[ ] 测试已运行。</li><li>[ ] lint / typecheck / 构建已运行或说明不能运行原因。</li><li>[ ] Git diff 已审查。</li><li>[ ] 无临时代码。</li><li>[ ] 无无关改动。</li><li>[ ] 验收标准逐条满足。</li></ul><p><strong>复盘清单</strong></p><ul><li>[ ] 这次是否暴露规范缺失？</li><li>[ ] 是否有重复 prompt 可以写成 skill？</li><li>[ ] 是否有新测试应该补？</li><li>[ ] 是否有 MCP 能减少复制粘贴？</li><li>[ ] 是否有项目规则已经不符合当前做法？</li><li>[ ] 是否有本次经验应该写进任务模板或 skill？</li></ul><h3 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h3><p>1.一句话开工。描述不清楚</p><p>开工前把四件事写清楚：</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">写 Goal <span class="symbol">/</span> Context <span class="symbol">/</span> Constraints <span class="symbol">/</span> Done When。</span><br></pre></td></tr></table></figure><p>2.网页端违章本地开发。把文件一段段复制给网页模型，让它生成补丁。</p><p>这样做容易制造表面正确但无法应用的补丁。网页模型看不到完整本地仓库，也跑不了测试</p><p>建议分工是：</p><figure class="highlight sqf"><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">Agent</span> 负责执行。</span><br></pre></td></tr></table></figure><p>3.规范文件写成作文</p><p>规范文件不是宣言。写太长、太虚，Agent 难以提取可执行约束。尤其是“写可验证、可维护的代码”“注意安全”这种话，看着正确，但没有可执行约束。</p><p>把规则改成短、明确、可验证：</p><figure class="highlight sqf"><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">短、硬、具体。</span><br><span class="line">每条规则问：删掉它，<span class="built_in">Agent</span> 会犯错吗？</span><br></pre></td></tr></table></figure><p>4.MCP装太多</p><p>MCP 不是越多越强。工具过多时，权限边界会变乱，调用噪声会变大，Agent 也更容易在多个相似工具里选错。</p><p>起步时只接最小集合：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">先只读、少量、明确场景。</span><br></pre></td></tr></table></figure><p>5.skills的复用</p><p>别人写的 skill 可以参考，但不能替你理解自己的流程。下载太多以后，常见结果是触发条件互相冲突，输出格式也和你的项目流程不匹配。</p><p>先从自己的高频流程提炼：</p><figure class="highlight sqf"><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">skill</span>。</span><br></pre></td></tr></table></figure><p>6.不看diff</p><p>聊天总结可能遗漏事实，diff 记录实际改动。最终发生了什么，只能看文件改动。AI 可能附带修改无关文件，也可能绕过已有抽象，风险也可能出现在一行看似无害的改动里。</p><p>不要跳过最后这一步：</p><figure class="highlight maxima"><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">diff</span>，不是聊天总结。</span><br></pre></td></tr></table></figure><p>7.完成不验证</p><p>“AI 说完成”只是一个声明，不是证据。</p><figure class="highlight erlang-repl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AI 说完成 != 项目已经完成。</span><br></pre></td></tr></table></figure><p>让它交代验证命令和结果；跑不了，也要说清楚为什么跑不了。</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">完成前必须运行或说明验证命令。</span><br></pre></td></tr></table></figure><p>8.把工具数量当成能力</p><p>同时装很多 MCP、skills、hooks 和多 Agent 框架，看起来像系统，实际可能只是更难解释的混乱。</p><p>先问缺口：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当前失败来自任务不清、上下文不足、执行不稳、权限太大，还是验证缺失？</span><br></pre></td></tr></table></figure><p>每次只补一层。补完后用一次真实任务验证它是否减少返工。</p><p>9.不写交接记录</p><p>长任务做到一半只靠聊天历史续命，很容易在上下文压缩、换工具或换人时断掉。</p><p>每次阶段结束都写四行：</p><figure class="highlight"><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></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></pre></td></tr></table></figure><p>这些内容应该进入任务目录或项目文档，而不是只留在对话里。</p><h3 id="跨阶段提示词库"><a href="#跨阶段提示词库" class="headerlink" title="跨阶段提示词库"></a>跨阶段提示词库</h3><p><a href="https://vibecoding.joytion.cn/_handbook/8-best-practices/5-prompt-library/">跨阶段提示词库 - Vibe Coding 实战手册</a></p><h3 id="端到端流程"><a href="#端到端流程" class="headerlink" title="端到端流程"></a>端到端流程</h3><p><img data-src="https://files.seeusercontent.com/2026/05/26/iG6b/image-20260526184539500.png" alt="image-20260526184539500"></p><h2 id="相关工具"><a href="#相关工具" class="headerlink" title="相关工具"></a>相关工具</h2><p>下面这些工具一般跟一些AI Coding工具搭配,包括一些hooks和skills</p><h3 id="SpecKit"><a href="#SpecKit" class="headerlink" title="SpecKit"></a>SpecKit</h3><blockquote><p>需求驱动开发彻底改变了传统的软件开发方式。几十年来，代码一直处于主导地位——需求文档只不过是临时性的框架而已，一旦开始真正的编码工作，这些需求文档就会被丢弃。而需求驱动开发则有所不同：需求文档可以被直接执行，从而生成可用的程序代码，而不仅仅是作为开发的指导依据。</p></blockquote><p><a href="https://speckit.org/">Spec Kit - AI-Powered Specification-Driven Development Toolkit</a></p><p><a href="https://github.com/github/spec-kit">github/spec-kit: 💫 Toolkit to help you get started with Spec-Driven Development</a></p><h3 id="OpenSpec"><a href="#OpenSpec" class="headerlink" title="OpenSpec"></a>OpenSpec</h3><p><a href="https://github.com/Fission-AI/OpenSpec">Fission-AI/OpenSpec: Spec-driven development (SDD) for AI coding assistants.</a></p><p><a href="https://github.com/Fission-AI/OpenSpec/blob/main/docs/workflows.md">OpenSpec/docs/workflows.md at main · Fission-AI/OpenSpec</a></p><h3 id="Superpowers"><a href="#Superpowers" class="headerlink" title="Superpowers"></a>Superpowers</h3><p><a href="https://github.com/obra/superpowers">obra/superpowers: An agentic skills framework &amp; software development methodology that works.</a></p><h3 id="Trellis"><a href="#Trellis" class="headerlink" title="Trellis"></a>Trellis</h3><p><a href="https://github.com/mindfold-ai/trellis">mindfold-ai/Trellis: The best agent harness.</a></p><p>流程:</p><ol><li>用自然语言描述你的需求</li><li>讨论问题，直到需求文档完善为止，之后再开始实施</li><li>AI调用 Trellis Implement 工具，自动检查结果是否符合各项规格要求、是否存在代码错误、类型是否正确以及是否通过了各项测试。</li><li>当工作完成或会话上下文已满时，输入 <code>/trellis:finish-work</code> 。Trellis 会自动保存该任务并更新相关记录。</li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://www.runoob.com/ai-agent/harness-engineering.html">Harness Engineering（驾驭工程） | 菜鸟教程</a></li><li><a href="https://github.com/riba2534/blog/blob/main/content/blog/2026/Effective-Harnesses-for-Long-Running-Agents.md">Effective-Harnesses-for-Long-Running-Agents.md at main · riba2534/blog</a></li><li><a href="https://zhuanlan.zhihu.com/p/2018446123845599997">从 Vibe Coding 到 Spec Coding 再到 Harness Engineer：AI 编程的三次范式跃迁 - 知乎</a></li><li><a href="https://openai.com/zh-Hans-CN/index/harness-engineering/">工程技术：在智能体优先的世界中利用 Codex | OpenAI</a></li><li><a href="https://harness-books.agentway.dev/index.html">Harness Books</a></li><li><a href="https://github.com/github/spec-kit">github/spec-kit: 💫 Toolkit to help you get started with Spec-Driven Development</a></li><li><a href="https://vibecoding.joytion.cn/_handbook/6-harness-engineering/10-six-harness-routes/">6.10 六条代表性 Harness 路线 - Vibe Coding 实战手册</a></li><li><a href="https://github.com/fengshao1227/ccg-workflow">fengshao1227/ccg-workflow: 多模型协作工作流引擎 — /ccg:go 一个命令，AI 自动分析意图、选择策略、编排 Codex + Gemini + Claude 协作执行</a></li><li><a href="https://jishuzhan.net/article/2053664082960646146">grill-me、Trellis、Superpowers：不同场景下怎么用？ - 技术栈</a></li><li><a href="https://juejin.cn/post/7605494530017165352#heading-10">AI 编程三剑客：Spec-Kit、OpenSpec、Superpowers 深度对比与实战指南AI 编程三剑客：Spe - 掘金</a></li><li><a href="https://check.linux.do/">LINUX DO - 模型中转状态检测</a></li><li><a href="https://www.deeplearning.ai/courses/spec-driven-development-with-coding-agents">https://www.deeplearning.ai/courses/spec-driven-development-with-coding-agents</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/05/24/Beyond-AI-Coding-Harness%E5%B7%A5%E7%A8%8B%E4%B8%8E%E6%9B%B4%E5%A5%BD%E5%9C%B0AI%E5%BC%80%E5%8F%91/</id>
    <link href="https://www.sekyoro.top/2026/05/24/Beyond-AI-Coding-Harness%E5%B7%A5%E7%A8%8B%E4%B8%8E%E6%9B%B4%E5%A5%BD%E5%9C%B0AI%E5%BC%80%E5%8F%91/"/>
    <published>2026-05-24T10:45:47.000Z</published>
    <summary>
      <![CDATA[<p>自AI Coding或者说Vibe Coding流行起来,开发者也意识到如何利用以及约束AI以写出更好维护、扩展的生产级代码越来越重要。Anthropic提出<a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">Harness工程</a>以长时间使用Agents生产代码。本篇文章主要介绍一下Harness工程以及相关实践。</p>]]>
    </summary>
    <title>Beyond AI Coding:Harness工程与更好地AI开发</title>
    <updated>2026-05-31T15:02:59.919Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="-cpp" scheme="https://www.sekyoro.top/tags/cpp/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>C++的一些概念复杂而且存在一些容易使用错误的地方,这里进行部分总结.<br><span id="more"></span></p><h1 id="基础"><a href="#基础" class="headerlink" title="基础"></a>基础</h1><h2 id="const关键字"><a href="#const关键字" class="headerlink" title="const关键字"></a>const关键字</h2><p><code>const</code> 是一个关键字，用于表示常量。</p><p><code>const</code> 可以用于修饰变量、函数、指针等：</p><p>1.修饰常量</p><p>当 <code>const</code> 修饰变量时，该变量将被视为只读变量，即不能被修改。</p><p>对于确定不会被修改的变量，应该加上 const，这样可以保证变量的值不会被无意中修改，也可以使编译器在代码优化时更加智能。</p><p>但理论上也可以赋给<code>const int*</code>再进行类型转换修改,</p><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> num = <span class="number">10</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span>* cp = &amp;num;</span><br><span class="line">    <span class="type">int</span>* p = <span class="built_in">const_cast</span>&lt;<span class="type">int</span>*&gt;(cp);</span><br><span class="line">    *p = <span class="number">20</span>;</span><br><span class="line">    cout&lt;&lt;num;</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>但编译器可能会进行优化,替换为实际值。因此<strong>应该遵循 C/C++ 语言中 const 的语义，尽量不修改只读变量的值。</strong></p><p>2.const修饰函数参数</p><p>当 <code>const</code> 修饰函数参数时，表示函数内部不会修改该参数的值。这样做可以使代码更加安全，避免在函数内部无意中修改传入的参数值。</p><p>3.修饰函数返回值</p><p>当 <code>const</code> 修饰函数返回值时，表示函数的返回值为只读，不能被修改。这样做可以使函数返回的值更加安全，避免被误修改。</p><p>4.修饰指针或引用</p><p>const 关键字可以用来修饰指针，用于声明指针本身为只读变量或者指向只读变量的指针。</p><p>4.1指向只读变量的指针</p><p>4.2只读指针</p><p>4.3只读指针指向只读变量</p><p>const 关键字同时修饰了指针本身和指针所指向的变量，使得指针本身和所指向的变量都成为只读变量。因此，指针本身不能被修改，也不能通过指针修改所指向的变量。</p><p>4.4常量引用</p><p>常量引用是指引用一个只读变量的引用，因此不能通过常量引用修改变量的值。</p><p>5.修饰成员函数</p><p>当 <code>const</code> 修饰成员函数时，表示该函数不会修改对象的状态（就是不会修改成员变量）。const 的对象就可以调用这些成员方法了，因为 const 对象不允许调用非 const 的成员方法。</p><p>const 的成员函数不能调用非 const 的成员函数，原因在于 const 的成员函数保证了不修改对象状态，但是如果调用了非 const 成员函数，那么这个保证可能会被破坏。</p><h2 id="static关键字"><a href="#static关键字" class="headerlink" title="static关键字"></a>static关键字</h2><p>可以用于变量和函数</p><p>1.static修饰全局变量</p><p>static 修饰全局变量可以将变量的<strong>作用域限定在当前文件中</strong>，使得其他文件无法访问该变量。<br> 同时，static 修饰的<strong>全局变量在程序启动时被初始化</strong>（可以简单理解为在执行 main 函数之前，会执行一个全局的初始化函数，在那里会执行全局变量的初始化），生命周期和程序一样长。</p><p>2.static修饰局部变量</p><p>static 修饰局部变量可以使得变量<strong>在函数调用结束后不会被销毁</strong>，而是一直存在于内存中，下次调用该函数时可以继续使用。</p><p>由于 static 修饰的局部变量的<strong>作用域仅限于函数内部</strong>，所以其他函数无法访问该变量</p><p>3.static修饰函数</p><p>static 修饰函数可以将函数的<strong>作用域限定在当前文件</strong>中，使得其他文件无法访问该函数。</p><p>由于 static 修饰的函数只能在当前文件中被调用，因此可以避免命名冲突和代码重复定义</p><p>4.static修饰类成员变量和函数</p><p>static 修饰类成员变量和函数可以使得它们在所有类对象中共享，且不需要创建对象就可以直接访问。</p><h2 id="volatile关键字"><a href="#volatile关键字" class="headerlink" title="volatile关键字"></a>volatile关键字</h2><p><code>volatile</code>是 C 语言中的一个关键字，用于修饰变量，表示该变量的值可能在任何时候被外部因素更改，例如硬件设备、操作系统或其他线程。</p><p>当一个变量被声明为<code>volatile</code>时，编译器会禁止对该变量进行优化，以确保每次访问变量时都会从内存中读取其值，而不是从寄存器或缓存中读取，避免因为编译器优化而导致出现不符合预期的结果。</p><p>即便是<code>volatile</code>关键字可以确保编译器不对变量进行优化，但上面任然存在并发问题，<code>counter++</code>操作仍然可能导致数据不一致。</p><p>为了解决这个问题，需要使用互斥锁、原子操作或其他同步机制。</p><h2 id="字节对齐"><a href="#字节对齐" class="headerlink" title="字节对齐"></a>字节对齐</h2><p>字节对齐是内存分配的一种策略。当分配内存时，编译器会自动调整数据结构的内存布局，使得数据成员的起始地址与其自然对齐边界（一般为自己大小的倍数）相匹配</p><p>理论上，任何类型的变量都可以从任意地址开始存放。然而实际上，访问特定类型的变量通常需要从特定对齐的内存地址开始。<strong>因为如果不对数据存储进行适当的对齐，可能会导致存取效率降低</strong></p><p>各种数据类型需要按照一定的规则在内存中排列（起始地址），而不是顺序地一个接一个排放，这种排列就是字节对齐。例如，有些平台每次读取都是从偶数地址开始。如果一个 int 类型（假设为 32 位系统）存储在偶数地址开始的位置，那么一个读周期就可以读取这 32 位。</p><p>但如果存储在奇数地址开始的位置，则需要两个读周期，并将两次读取的结果的高低字节拼凑才能得到这 32 位数据,显然这会显著降低读取效率。</p><p>为了避免性能损耗，编译器会遵循一个基本原则：<strong>变量的内存起始地址，必须是该变量自身大小的整数倍</strong></p><p><strong>总结： 字节对齐有助于提高内存访问速度，因为许多处理器都优化了对齐数据的访问。但是，这可能会导致内存中的一些空间浪费。</strong></p><p><strong>字节对齐规则</strong></p><p><strong>1. 自然对齐边界</strong></p><p>对于基本数据类型，其自然对齐边界通常为其大小。</p><p>例如，<code>char</code> 类型的自然对齐边界为 1 字节，<code>short</code> 为 2 字节，<code>int</code> 和 <code>float</code> 为 4 字节，<code>double</code> 和 64 位指针为 8 字节。具体数值可能因编译器和平台而异。</p><p><strong>2. 结构体对齐</strong></p><p>结构体内部的每个成员都根据其自然对齐边界进行对齐,也就是可能在成员之间插入填充字节。</p><p>结构体本身的总大小也会根据其最大对齐边界的成员进行对齐（比如结构体成员包含的最长类型为int类型，那么整个结构体要按照4的倍数对齐），以便在数组中正确对齐。</p><p><strong>3.联合体对齐</strong></p><p>联合体的对齐边界取决于其最大对齐边界的成员。联合体的大小等于其最大大小的成员，因为联合体的所有成员共享相同的内存空间。</p><p><strong>4.编译器指令</strong></p><p>可以使用编译器指令（如 <code>#pragma pack</code>）更改默认的对齐规则。这个命令是全局生效的。这可以用于减小数据结构的大小，但可能会降低访问性能。</p><p><strong>5.对齐属性</strong></p><p>在 C++11 及更高版本中，可以使用 <code>alignas</code> 关键字为数据结构或变量指定对齐要求。这个命令是对某个类型或者对象生效的。例如，<code>alignas(16) int x;</code> 将确保 <code>x</code> 的地址是 16 的倍数</p><p><strong>6.动态内存分配</strong></p><p>大多数内存分配函数（如 <code>malloc</code> 和 <code>new</code>）会自动分配足够对齐的内存，以满足任何数据类型的对齐要求。</p><h2 id="字节序"><a href="#字节序" class="headerlink" title="字节序"></a>字节序</h2><p>字节序是指在<strong>多字节数据类型（如整数、浮点数等）中，字节在内存中的存储顺序</strong>。主要有两种字节序：大端字节序（Big-endian）和小端字节序（Little-endian）</p><p><strong>大端字节序</strong>: 高位字节存储在低地址处，低位字节存储在高地址处。例如，一个4字节的整数0x12345678，在大端字节序的系统中，</p><p><strong>小端字节序</strong>: 低位字节存储在低地址处，高位字节存储在高地址处。</p><p><strong>常见大小端字节序</strong></p><p><strong>1. 网络传输</strong></p><p>在网络传输过程中，通常使用大端字节序（Big-endian），也称为网络字节序，这是 TCP/IP 协议的规定，多字节数据在网络上传输时使用大端字节序。</p><p>因此，如果本地系统使用的是小端字节序，那么就需要在传输之前将其转换为大端字节序。一般通过使用<code>htonl()</code>、<code>htons()</code>、<code>ntohl()</code>和<code>ntohs()</code>等函数来完成</p><p><strong>2. Linux</strong></p><p>Linux 操作系统在不同的硬件平台上可能使用不同的字节序。例如，x86 和 x86_64（Intel 和 AMD 处理器）是小端字节序（Little-endian），而 PowerPC 和 SPARC 等其他架构可能使用大端字节序（Big-endian）。</p><p>所以具体的字节序取决于运行 Linux 的硬件平台。</p><p><strong>3. Windows</strong></p><p>Windows 操作系统主要运行在 x86 和 x86_64（Intel 和 AMD处理器）架构上，这些处理器使用小端字节序（Little-endian）。</p><p><strong>4. Mac</strong></p><p>一般使用 Intel 处理器或 Apple 自家的 M1 芯片（基于ARM架构），这些处理器都采用小端字节序（Little-endian）</p><h2 id="class类和struct结构体差别"><a href="#class类和struct结构体差别" class="headerlink" title="class类和struct结构体差别"></a>class类和struct结构体差别</h2><p>在 C++ 中，<code>class</code>（类）和 <code>struct</code>（结构体）的底层实现和核心能力<strong>几乎完全相同</strong>。</p><p>C++ 的 <code>struct</code> 继承自 C 语言，但进行了全面魔改——在 C++ 里，<code>struct</code> 同样可以拥有<strong>构造函数、析构函数、成员函数、虚函数，并且支持继承和多态</strong>。</p><p>差别:</p><ol><li>默认的访问权限（最核心区别）</li></ol><ul><li><strong><code>struct</code> 的成员默认是公有的（<code>public</code>）</strong>。</li><li><strong><code>class</code> 的成员默认是私有的（<code>private</code>）</strong>。</li></ul><ol><li>默认的继承权限</li></ol><p>当一个类继承自另一个类/结构体且没有显式指定继承方式时：</p><ul><li><strong><code>struct</code> 默认是公有继承（<code>public</code>）</strong>。</li><li><strong><code>class</code> 默认是私有继承（<code>private</code>）</strong></li></ul><ol><li>模板参数中的微小差别（<code>template</code>）</li></ol><p>在声明泛型模板时，你可以使用 <code>class</code> 或 <code>typename</code>，但<strong>不能使用 <code>struct</code></strong></p><p><strong>适合用</strong> <code>struct</code> 的场景：</p><p>当你需要定义一个“纯粹的数据集合”，它只包含一堆变量，没有复杂的业务逻辑和封装保护（类似 C 语言的结构体或 DTO / POJO）时，用 <code>struct</code>。</p><blockquote><p><strong>特征</strong>：成员全是 <code>public</code>，通常没有或只有简单的构造函数。</p></blockquote><p>在 C++11 及之后的标准中，POD 类型需要同时满足两个独立条件：</p><p>1.<strong>平凡（Trivial）</strong>：类型具有默认的构造/拷贝/移动/析构函数（可自动生成且非虚）<br> 2.<strong>标准布局（Standard Layout）</strong>：内存布局与 C 兼容，成员排列顺序符合特定规则</p><p>同时满足平凡性和标准布局的类型称为 POD 类型，这类数据可以安全使用 memcpy 等底层内存操作，因为它们的内存布局与 C 完全兼容且没有特殊处理需求。</p><p><strong>适合用</strong> <code>class</code> 的场景：</p><p>当你的对象具有复杂的内部状态、需要隐藏实现细节、对外提供特定接口（面向对象的核心思想）时，用 <code>class</code>。</p><blockquote><p><strong>特征</strong>：包含 <code>private</code> 内部变量，对外暴露 <code>public</code> 的方法（如 Getter/Setter、业务函数），有严格的封装性。</p></blockquote><h2 id="宏定义和内联函数的区别"><a href="#宏定义和内联函数的区别" class="headerlink" title="宏定义和内联函数的区别"></a>宏定义和内联函数的区别</h2><p>宏定义（<code>#define</code>）和内联函数（<code>inline</code>）都是为了<strong>减少函数调用开销和提高代码运行效率而引入的机制</strong>，但是它们的实现方式和作用机制略有不同。</p><p><code>#define</code>本质是文字替换,在编译之前，预处理器（Preprocessor）会扫描你的代码。看到 <code>MAX(a, b)</code>，它就机械地把这一行字擦掉，换成后面的三目运算符表达式。编译器在编译时，根本不知道 <code>MAX</code> 这个名字的存在，它看到的已经是替换后的臃肿表达式了。</p><p>写宏函数一定要注意：参数和函数体应当用括号包围，避免因运算优先级导致的错误</p><p><code>inline</code> 声明的函数是一个真正的函数。</p><ul><li>编译器在编译它时，会进行<strong>语法分析、类型检查、模板特化</strong>。</li><li>当遇到调用处时，编译器会根据当前上下文的优化策略，自主决定要不要把它“内联展开”。</li><li><strong>特别注意</strong>：<code>inline</code> 对编译器来说只是一个<strong>建议</strong>（Suggestion），而不是强制命令。如果你的内联函数内部写了复杂的循环、递归，或者代码量巨大，编译器会理智地拒绝内联，依然把它当做普通函数来调用。</li></ul><blockquote><p>函数内联展开本质减少了函数调用压栈,跳转,出栈等成本.针对本身非常简短的函数内联减少调用成本.</p><p>内联展开遵循<strong>空间换时间</strong>的原则。每一次展开，都会让最终生成的二进制可执行文件（<code>.exe</code> 或 <code>.so</code>）体积变大。</p><ul><li><strong>如果不内联</strong>：函数在内存中只有一份拷贝。哪怕被调用 1000 次，也只是跳过去执行那同一段代码，程序体积很小。</li><li><strong>如果强制内联</strong>：被调用 1000 次，这段代码就会在不同的地方<strong>被完整复制 1000 遍</strong>。</li></ul></blockquote><p>在现代 C++（C++11 及更新标准）中，为了安全和可维护性，我们应当<strong>尽量避免使用 <code>#define</code> 来定义常量和函数逻辑</strong>。</p><ul><li><strong>需要定义常量时</strong>：用 <code>const</code> 或 <code>constexpr</code> 代替 <code>#define</code>。</li><li><strong>需要定义简短的高频函数时</strong>：用 <code>inline</code>（或者类的内联成员函数）代替 <code>#define</code>。</li><li><strong>什么场景下还必须用 <code>#define</code>？</strong>：只有在需要用到<strong>条件编译</strong>（如 <code>#ifndef _DEBUG</code>）或者<strong>宏拼接符号</strong>（如 <code>#</code> 和 <code>##</code> 进行动态变量名/日志打印生成）时，才是 <code>#define</code> 不可替代的舞台。</li></ul><h2 id="define和typedef区别"><a href="#define和typedef区别" class="headerlink" title="define和typedef区别"></a>define和typedef区别</h2><p>预处理器只进行简单的文本替换，不涉及类型检查。与宏定义不同，typedef 是在编译阶段处理的，有更严格的类型检查。</p><p>宏定义没有作用域限制，只要在宏定义之后的地方，就可以使用宏。</p><p>通常用于定义常量、简单的表达式或简单的代码片段。</p><p>typedef 遵循 C++ 的作用域规则，可以受到命名空间、类等结构的作用域限制。typedef 通常用于定义复杂类型的别名，使代码更易读和易于维护</p><p>宏定义不支持模板，因此不能用于定义模板类型别名。</p><p>typedef 可以与模板结合使用，但在 C++11 之后，推荐使用 using 关键字定义模板类型别名。</p><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 typedef 定义模板类型别名</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">MyContainer</span> &#123;</span><br><span class="line">    <span class="keyword">typedef</span> std::vector&lt;T&gt; Type;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 using 定义模板类型别名（C++11 及以后）</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">MyContainer</span> &#123;</span><br><span class="line">    <span class="keyword">using</span> Type = std::vector&lt;T&gt;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="explicit关键字"><a href="#explicit关键字" class="headerlink" title="explicit关键字"></a>explicit关键字</h2><p><code>explicit</code> 通常用于构造函数的声明中，用于防止隐式转换</p><p>当将一个参数传递给构造函数时，如果构造函数声明中使用了 <code>explicit</code> 关键字，则只能使用显式转换进行转换，而不能进行隐式转换。这种机制可以防止编译器自动执行预期外的类型转换，提高代码的安全性。</p><p>隐式类型转换：当你只有一个类型T1，但是当前表达式需要类型为T2的值，如果这时候T1自动转换为了T2，那么这就是隐式类型转换。</p><p>可以使用 <code>explicit</code> 关键字可以防止不必要的隐式转换，提高代码的可读性和安全性。</p><p>尤其是构造函数参数只有一种类型的，强烈建议加上 <code>explicit</code></p><h2 id="extern作用"><a href="#extern作用" class="headerlink" title="extern作用"></a>extern作用</h2><p>一般而言，C++全局变量的作用范围仅限于当前的文件，但同时C++也支持分离式编译，允许将程序分割为若干个文件被独立编译,于是就需要在文件间共享变量数据，这里extern就发挥了作用。</p><p><code>extern</code> 用于指示变量或函数的定义在另一个源文件中，并在当前源文件中声明。<br> 说明该符号具有外部链接(external linkage)属性。</p><p>也就是告诉编译器: 这个符号在别处定义了，你先编译，到时候<strong>链接器会去别的地方找这个符号定义的地址</strong>。</p><p><strong>凡是没有带extern的声明同时也都是定义</strong>。<br>而对函数而言，带有{}是定义，否则是声明。如果想声明一个变量而非定义它，就在变量名前添加关键字<code>extern</code>，且不要显式的初始化变量。</p><p><strong>链接属性</strong></p><p>在 C++ 中，<strong>链接属性是指程序在编译、链接和执行阶段如何处理符号（变量、函数、类等)的可见性和重复定义</strong>。</p><p>外部链接: 外部链接的符号可以在不同的源文件之间共享，并且在整个程序执行期间可见。全局变量和函数都具有外部链接。</p><p>内部链接: 内部链接的符号只能在当前源文件内部使用，不能被其他源文件访问。用 <code>static</code> 修饰的全局变量和函数具有内部链接</p><p>无链接的符号只能在当前代码块（函数或代码块）内部使用，不能被其他函数或代码块访问。用 <code>const</code> 或 <code>constexpr</code> 修饰的常量具有无链接属性（ 通常情况下编译器是不会为const对象分配内存，也就无法链接）。</p><p>外部 C 链接的符号与外部链接类似，可以在不同的源文件之间共享，并且在整个程序执行期间可见。它们具有 C 语言的名称和调用约定，可以与 C 语言编写的代码进行交互。在 C++ 中，可以用 <code>extern &quot;C&quot;</code> 关键字来指定外部 C 链接，从而使用一些 C 的静态库。</p><p>这些链接属性可以通过关键字 <code>extern</code>、<code>static</code>、<code>const</code> 和 <code>extern &quot;C&quot;</code> 来显式地指定。在实际的开发中，正确地理解和处理链接属性对于编写可重用、高效、可维护的代码非常重要</p><p>编译链接过程中，<code>extern</code> 的作用如下：</p><ol><li>在<strong>编译期</strong>，<code>extern</code> 用于告诉编译器某个变量或函数的定义在其他源文件中，编译器会为它生成一个符号表项，并在当前源文件中建立一个对该符号的引用。</li></ol><p>这个引用是一个未定义的符号，编译器在后续的链接过程中会在其他源文件中查找这个符号的定义。</p><ol><li>在<strong>链接期</strong>，链接器将多个目标文件合并成一个可执行文件，并且在当前源文件中声明的符号，会在其它源文件中找到对应的定义，并将它们链接起来</li></ol><h2 id="extern-C"><a href="#extern-C" class="headerlink" title="extern C"></a>extern C</h2><p>如果不用 extern C，由于 C++ 和 C 语言在编译和链接时使用的命名规则不同，这会导致 C++ 代码无法调用 C 语言编写的函数或变量（链接时找不到符号）</p><p><strong>函数命名规则</strong></p><p>由于需要支持<strong>重载</strong>,一个函数的链接名（Linkage Name）是由函数的名称、参数类型和返回值类型等信息组成的，<strong>用于在编译和链接时唯一标识该函数</strong>。函数的链接名的生成规则在不同的编译器和操作系统上可能有所不同，一般是由编译器自动处理，不需要手动指定，这个规则常常叫做Name Mangling</p><p>常见的规则：</p><ul><li><strong>Microsoft Visual C++ 编译器（Windows）</strong>：函数的名称会被编译器修改为一个以 “?” 开头的名称，并加上参数类型和返回值类型等信息，以避免链接冲突。例如，函数 <code>int add(int a, int b)</code> 的链接名可能是 <code>?add@@YAHHH@Z</code>。</li><li><strong>GCC 编译器（Linux）</strong>：也会加上参数类型和返回值类型等信息。例如，函数 <code>int add(int a, int b)</code> 的链接名可能是 <code>_Z3addii</code>。</li><li><strong>Clang 编译器（MacOS）</strong>：函数的链接名的生成规则与 GCC 编译器类似，但稍有不同。例如，函数 <code>int add(int a, int b)</code> 的链接名可能是 <code>_Z3addii</code>。</li></ul><p>而 C 语言的链接函数名规则又和 上面三个 C++ 不一样，通过在 C++ 代码中使用 <code>extern &quot;C&quot;</code> 关键字，可以将 C++ 编译器的命名规则转换为 C 语言的命名规则，从而使得 C++ 代码可以调用 C 语言的函数或变量。</p><blockquote><p>Name Mangling（名称修饰 / 改名机制）是编译器在编译源代码时，将变量、函数或方法的原始名称，转化为包含更多上下文信息的“独特新名称”的技术。</p></blockquote><p>使用 <code>extern &quot;C&quot;</code> 声明的函数或变量会采用 C 语言的链接规则，即符号的名称和调用约定与 C 语言相同。</p><p>需要注意 <code>extern &quot;C&quot;</code> 关键字只对函数的名称和调用约定起作用，对于函数的参数类型和返回值类型没有影响。所以，在使用 <code>extern &quot;C&quot;</code> 声明函数时，需要保证函数的参数类型和返回值类型与 C 语言的定义相同，否则可能会导致编译错误或运行时错误。</p><h2 id="mutable作用"><a href="#mutable作用" class="headerlink" title="mutable作用"></a>mutable作用</h2><p><code>mutable</code>是C++中的一个关键字，用于修饰类的成员变量，表示该成员变量即使在一个<code>const</code>成员函数中也可以被修改。因为在C++中，如果一个成员函数被声明为<code>const</code>，那么它不能修改类的任何成员变量，除非这个成员变量被声明为<code>mutable</code>。</p><p>如果需要在const函数里面修改一些跟类状态无关的数据成员，那么这个函数中对应的成员就应该被mutable来修饰。允许在<code>const</code>成员函数中修改特定的成员变量，以支持内部实现所需的功能，同时仍然保持外部不变性。</p><h2 id="四种强制类型转换"><a href="#四种强制类型转换" class="headerlink" title="四种强制类型转换"></a>四种强制类型转换</h2><p>在 C++ 中，更推荐使用四个转换操作符来实现显式类型转换：</p><ul><li>static_cast</li><li>dynamic_cast</li><li>const_cast</li><li>reinterpret_cast</li></ul><p><strong>static_cast</strong></p><ol><li>静态(编译期检查),将一个基本类型转为另一个基本类型</li><li>指针类型之间转换.将一个指针类型转换为另一个指针类型，尤其是在类层次结构中从基类指针转换为派生类指针。这种转换不执行运行时类型检查，可能不安全，要自己保证指针确实可以互相转换。</li><li>引用类型之间的转换</li></ol><p><code>static_cast</code>在编译时执行类型转换，在进行指针或引用类型转换时，需要自己保证合法性。</p><p>如果想要运行时类型检查，可以使用<code>dynamic_cast</code>进行安全的向下类型转换。</p><p><strong>dynamic_cast</strong></p><p><code>dynamic_cast</code>在C++中主要应用于父子类层次结构中的安全类型转换。它在运行时执行类型检查，因此相比于<code>static_cast</code>，它更加安全。</p><p><code>dynamic_cast</code>在C++中主要应用于父子类层次结构中的安全类型转换。</p><p>它在运行时执行类型检查，因此相比于<code>static_cast</code>，它更加安全。</p><blockquote><p>C++ 的 RTTI 信息是存放在虚函数表（vtable）里的。如果基类没有虚函数，它就不会生成虚函数表，<code>dynamic_cast</code> 也就失去了赖以生存的底层数据，编译器会直接报错。</p></blockquote><h1 id="面向对象"><a href="#面向对象" class="headerlink" title="面向对象"></a>面向对象</h1><h2 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h2><p>C++中的多态是指同一个函数或者操作在不同的对象上有不同的表现形式。</p><p><strong>C++实现多态的方法主要包括虚函数、纯虚函数和模板函数</strong></p><p>其中虚函数、纯虚函数实现的多态叫动态多态，模板函数、重载等实现的叫静态多态。</p><p>区分静态多态和动态多态的一个方法就是看决定所调用的具体方法是在编译期还是运行时，运行时就叫动态多态。</p><h2 id="虚函数、纯虚函数实现多态"><a href="#虚函数、纯虚函数实现多态" class="headerlink" title="虚函数、纯虚函数实现多态"></a>虚函数、纯虚函数实现多态</h2><p>在 C++ 中，可以使用虚函数来实现多态性。</p><p>虚函数是指在基类中声明的函数，它在派生类中可以被重写。</p><p>当我们使用基类指针或引用指向派生类对象时，通过虚函数的机制，可以调用到派生类中重写的函数，从而实现多态。</p><p>C++ 的多态必须满足两个条件：</p><ul><li><strong>必须通过基类的指针或者引用调用虚函数</strong></li><li><strong>被调用的函数是虚函数，且必须完成对基类虚函数的重写</strong></li></ul><p><strong>虚函数表</strong></p><p>虚函数是通过一张虚函数表（Virtual Table）来实现的，简称为V-Table。在这个表中，存放的是一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其真实反应实际的函数。</p><p>这个类的实例内存中都有一个虚函数表的指针，所以，当用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。</p><p><strong>在底层，当一个类声明一个虚函数时，编译器会为该类创建一个虚函数表（Virtual Table）。</strong><br> 这个表存储着该类的虚函数指针，这些指针指向实际实现该虚函数的代码地址。</p><p>每个对象都包含一个指向该类的虚函数表的指针，这个指针在对象创建时被初始化，通常是作为对象的第一个成员变量。</p><p>当调用一个虚函数时，编译器会通过对象的虚函数指针查找到该对象所属的类的虚函数表，并根据函数的索引值（通常是函数在表中的位置，编译时就能确定）来找到对应的虚函数地址。</p><p>然后将控制转移到该地址，实际执行该函数的代码。对于派生类，其虚函数表通常是在基类的虚函数表的基础上扩展而来的。<strong>在派生类中，如果重写了基类的虚函数，那么该函数在派生类的虚函数表中的地址会被更新为指向派生类中实际实现该函数的代码地址。</strong></p><p>C++的动态多态必须满足两个条件：</p><ul><li><strong>必须通过基类的指针或者引用调用虚函数</strong></li><li><strong>被调用的函数是虚函数，且必须完成对基类虚函数的重写</strong></li></ul><p>其中第一条很重要，当我们使用派生类的指针去访问/调用虚函数时，实际上并未发生动态多态，因为编译时就能确定对象类型为派生类型，然后直接生成调用派生类虚函数的代码即可，这种叫做<strong>静态绑定</strong>。通过基类的指针或引用调用虚函数才能构成多态，因为这种情况下运行时才能确定对象的实际类型，这种称为<strong>动态绑定</strong></p><p><strong>纯虚函数</strong></p><p>纯虚函数是一种在基类中声明但没有实现的虚函数。</p><p><strong>它的作用是定义了一种接口，这个接口需要由派生类来实现。</strong></p><p>包含纯虚函数的类称为抽象类（Abstract Class）。抽象类仅仅提供了一些接口，但是没有实现具体的功能。作用就是制定各种接口，通过派生类来实现不同的功能，从而实现代码的复用和可扩展性。</p><p>另外，抽象类无法实例化，也就是无法创建对象。原因很简单，纯虚函数没有函数体，不是完整的函数，无法调用，也无法为其分配内存空间。</p><p>虚函数使用了一种称为虚函数表（vtable）的机制。然而，在调用构造函数时，对象还没有完全创建和初始化，所以虚函数表可能尚未设置。</p><p>这意味着在构造函数中使用虚函数表会导致未定义的行为。只有执行完了对象的构造，虚函数表才会被正确的初始化。</p><h1 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h1><h2 id="RAII"><a href="#RAII" class="headerlink" title="RAII"></a>RAII</h2><p>资源获取即初始化（Resource Acquisition Is Initialization，简称 RAII）是一种 C++ 编程技术，它将在使用前获取（分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥量、磁盘空间、数据库连接等有限资源）的资源的生命周期与某个对象的生命周期绑定在一起。</p><p>确保在控制对象的生命周期结束时，按照资源获取的相反顺序释放所有资源。</p><p>同样，如果资源获取失败（构造函数退出并带有异常），则按照初始化的相反顺序释放所有已完全构造的成员和基类子对象所获取的资源。</p><p>这利用了核心语言特性（对象生命周期、作用域退出、初始化顺序和堆栈展开），以消除资源泄漏并确保异常安全。</p><p>在实际的 C/C++ 开发中，我们经常会遇到诸如 coredump、segmentfault 之类的内存问题，使用指针也会出现各种问题，比如:</p><ul><li>野指针：未初始化或已经被释放的指针被称为野指针</li><li>空指针：指向空地址的指针被称为空指针</li><li>内存泄漏：如果在使用完动态分配的内存后忘记释放，就会造成内存泄漏，长时间运行的程序可能会消耗大量内存。</li><li>悬空指针：指向已经释放的内存的指针被称为悬空指针</li><li>内存泄漏和悬空指针的混合：在一些情况下，由于内存泄漏和悬空指针共同存在，程序可能会出现异常行为。</li></ul><p>智能指针是一种可以自动管理内存的指针，它可以在不需要手动释放内存的情况下，确保对象被正确地销毁。</p><p>这种指针可以显著降低程序中的内存泄漏和悬空指针的风险。</p><p>在C++中，智能指针常用的主要是两个类实现：</p><ul><li>std::unique_ptr</li><li>std::shared_ptr</li></ul><p>智能指针是一种可以自动管理内存的指针，它可以在不需要手动释放内存的情况下，确保对象被正确地销毁。这种指针可以显著降低程序中的内存泄漏和悬空指针的风险。</p><p>std::unique_ptr是一个独占所有权的智能指针，它保证指向的内存只能由一个unique_ptr拥有，不能共享所有权。</p><p>当unique_ptr超出作用域时，它所指向的内存会自动释放。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="function">std::unique_ptr&lt;<span class="type">int</span>&gt; <span class="title">ptr</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line">    std::cout &lt;&lt; *ptr &lt;&lt; std::endl; <span class="comment">// 输出10</span></span><br><span class="line">    <span class="comment">// unique_ptr在超出作用域时自动释放所拥有的内存</span></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>std::shared_ptr是一个共享所有权的智能指针，它允许多个shared_ptr指向同一个对象，当最后一个shared_ptr超出作用域时，所指向的内存才会被自动释放。</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;<span class="type">int</span>&gt; <span class="title">ptr1</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line">    std::shared_ptr&lt;<span class="type">int</span>&gt; ptr2 = ptr1; <span class="comment">// 通过拷贝构造函数创建一个新的shared_ptr，此时引用计数为2</span></span><br><span class="line">    std::cout &lt;&lt; *ptr1 &lt;&lt; <span class="string">&quot; &quot;</span> &lt;&lt; *ptr2 &lt;&lt; std::endl; <span class="comment">// 输出10 10</span></span><br><span class="line">    <span class="comment">// ptr2超出作用域时，所指向的内存不会被释放，因为此时ptr1仍然持有对该内存的引用</span></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><p>但需要注意的是，智能指针不是万能的，也并不是一定要使用的，有些场景下手动管理内存可能更为合适</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://csguide.cn/cpp/">一文搞懂 C/C++ 面试重点知识和常见面试题（2025 年更新） | 编程指北-计算机学习指南</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/04/16/C-%E9%87%8D%E7%82%B9%E7%9F%A5%E8%AF%86%E5%9B%9E%E9%A1%BE/</id>
    <link href="https://www.sekyoro.top/2026/04/16/C-%E9%87%8D%E7%82%B9%E7%9F%A5%E8%AF%86%E5%9B%9E%E9%A1%BE/"/>
    <published>2026-04-16T09:24:04.000Z</published>
    <summary>
      <![CDATA[<p>C++的一些概念复杂而且存在一些容易使用错误的地方,这里进行部分总结.<br>]]>
    </summary>
    <title>C++重点知识回顾</title>
    <updated>2026-10-02T06:36:59.708Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="抱歉，您输入的密码错误，请检查后重新输入。" data-whm="抱歉, 当前文章不能被校验, 不过您还是可以看看解密后的内容。">  <script id="hbeData" type="hbeData" data-hmacdigest="c3cc9e6b65aabee8ffe852ec1f24549abda2607d36743dafd26557713c07fa93">1ec2dee17da1b1b57a583b6340ef2233c9b72065fac3a317b2c8ecb37716c370ea1cd3cc30477f5679d264276c83dd344e8df799680860c4770b2df8be3888625bb353adbb256b201e1d15d80835c1d6512470792b21b2db659d9b5215c2365f0b2495eb2bfbb83136d2666e47bffc420c5ea531011b1918ae0bfb80b1e7012ca2f1f64a503634b53e99092cab77108ff731132ab080317a7eaf49945f9020755c14b9db3137e2890dc519a9c84ce906b24551599e52ab1cb507edf76b13887f343ff8399345c570ed571148f27a954ce38f4b8bcb9fc5ce286a7b905bab2489d3968251810efb70a0b679afd4ae3c6d1912cfe6d5e6b920775ef41ecf44198c0f56c524983e1ad6e06671bdd9c18f20a9d4eddd1eaeb1eaef556f2e6b68d3eb17fbc4e6c12580f7b3dad3c0018da1e5af1fb16fd756ba27884bd3d53eb5bb305a5b5b71b3c24ea1fa0c886c59a1f635b61727a7925f2da9f3af86849811c4763e81f9d403acb1bc2b977b3bd7a11a8c5bbc9d565552cb201fa63605527a9cceeca949b8f0dbb38c382ecfea0e1e56ac879371dc585d24a39d6384558e4164893bed2996c0908183ec666a5c2ad122dba88da4f6e5926d9d7dc23b9b63c657b4af95ecb8c2b6e562912815dd805b23eb538b6476f79a93bec9cf8267b495fa017fe1eccd9fa1fecb7358dc5d7d1d3d4833b2956c2ad8d5ede117c8a502c3738de765b5880adfb7508a873db2a407d38e2af3535c1c721c732cf37baaf58f7412f3c88135adcd26a2dffd4e1b2dae3a3e0051aaf42e9bf7abb34a50bb1ae817629be174625eb08494c4d2b670ec621fd67f23a57bc57acf8dfd767fcf0a916af3cf843f7d1ec82cc5be5a4b8cb06c6437652a945fd3c7c4e994fee8c9098ccd349a3673ea921165ee754294c383a1b06e6b823527f1a3c2bfbf51ab060525be11a883d5a6faba6b8f421540beb5198d44f2a9431457ead29afea9563db059a96246de0d9a5fccc591ac204f16088da1f9cdef0449043fed0f28f2911a313a60ed1fb53abbab750b06e5a68fa00038aa99aedd39f4ec15e07232fd56217f591bbd134a36567fa758a989a18a9f03361bbec98655f3852ba1c01f5a9679ae863315c59fe9b28c0fd3b5277b61e069aee048213b79659123609dbe9927573e0d3cb9eb29e5660a34528d9fcc95507a5f391d30381f055e9f2af0e8342ddde3e354ec8825affeb883fff12e816bf0d12ad6b586cdb2159499a82a711acfc82901a87aad0e4dd6892670cecbbcb5b3ad954582ceee33637339374955c010e3f9b72885ff1b7a586df8590068b1c881896a526d81aaef6cc58c9fb5206e679a49e74a5efb94d5dc614cd6a5b272e5c1a65a86be38b49870c3ee16608c15ee8c26eb2e6d874ec5d07696234c08fd4f5d841bf3439f048004f40415237df5e570df66d4574a0008931d48df5b5fef875c17d1fb3aa1bfd284b319405bff1756cb0d915c03267ee800ad64fce3af962db7cb6b2c73609663ead3b36876d4eed9ce4202c817451fa8d12a7e6c0f5dac61d4a02cbe93ecb2871845c847d00f6e86c126480d9c9aeccdfe662a2912dd27175cd8cb242191453db6d11ab361471038171ecb4a1a4afb066c9dec939ff7eacb7899661ecf7e113ab3b1e6fac7b2238698e761213603aeec33689d08a1135baa34f8874b0c62d2174c2c9b8a477d983a1e3142015f75349b51fb88c938215cf176f4d189f9219237a6c8cd10fb0fa9598335ee2d1b7b3e703c594d7f730cd730e9fb12522e6ec284456590cbbb9d01b4ff3823701ae4649fd4dfac2ce319225b9a385f869d0ddb975fcf88790f179c30656751fb6383d4a922278665fb895cc40ee677d86ce9d47b4759b38d192ef4e42e3399420885f1a087d24a4cad6eb7a826366c7f6187011c6996f28d254e028aa9d27185b924ab9c1fb8648113640454ac714da52ccea50dade4a6d04552f4b697bb4bb64774441b443889e4469e19a71a0b0324117dc54e1dd3401462f8d877f45cee6432e44f71f2804f3b2f46a3e28370ff5553ee066c0847cad987f4d5fa0788639ee7d194f32d4a84b48cc508ef49ace5ad200894e0eeefd3f94bbd54bdc0d388fca338a439c534ad90ec471d544867936ee3a97ea65215e6bd06c6ea8ff31edf3dca87998559faa5dd7da8ba080bafb7a8c456d4f2c40688ddce17bb8b3cbecaacbf2dfb772543d1e75367e0d6f2ecb1e1ce8af1ff62ffb6d9e241cce95aaed5da2a5e987097644abd6f59780c8035766279a0f751d78c12f3535ee6b383574512feb429b95b454ad9f55a22fae607fbee6a10c70eb859fe4c77a550f14de8c5fa753681522b22a52ea886fa67b88d4394b1801e1c032115cc0e1e4e4592340aa6656284cd17e831943e67a107c8812b9aa0ea195832b5de0bbfc34dcc84b1ada99aef504640d917a3bb31f69d09b70f069cc576e578ecf27b82eef0370628d91c1b120ef465cb61b47ffae2f1e2007ca0d9821a379dc915b3d57a60425a84be7c689eb67c03ab648da0ce81006f69d3e3399ce39efdf453061a4afb67f2ee50b5fd53fd009cd5f06a5d17360d9749edca6f378774b23a2778484c2a2dd70ebb3b33a7533872e8690ee0930b848e1f4ec7cd4cbe06077d062cb3d635ab961231f72521535aaf90915b08447a58c0d2f5ef07bbbdf705d2ad9297d4c0f06f66700b15e47986437b489083f52014b627b5ba9cc657a6600ab482289f9c94626be0815eea2bbcbf9543edea415ed4a8e5b8b4faf24b73256f9260762fd6315c74fa19cb287210060158388caa58e85b512929515124f730d47fb2d78ae967e7b9733b4114dafb358dc6025aeb402903cdb5374885c3c81e123f7b171dc7b7b0f8ca8cccb0b1476e8d9d467e83dccab8b4cd253dcbc6f92bbf58f8b8663ea041ce906a215fe8363f7a3847b397b9a445b551b06ac7fa3addcb99fb999b0971e4e85e78d3a42296b502aa3200d0a3aefefbd6d4ac8ef588f5afb2eaf74dd5527793247d5c441133aee1eb87587f60926853ece445ae6dcd5d4032c57f5a9294fad05fc06585416e3b5a754623aeb7846fa1d2e81a17a3de7a1062905dbc650860a46c5ef9e1f32c495a63ccb3e80958b33313e117d03f741990cd2fc86ef30117ad4ace0001d1dc532856ee8a497ac49cca56386934bc453ced46629f7c17c80b400d7e88252267a3bd3a3f827c2311366e0bae9b5b6997894e4bf389a1daa370cd8cfd7c5203b07279d5f786ba637fd3ea42d6a698a84a207f01c59f5eb3745a611cf9b46de92af065a05cb508f2f556cfeeb22d61659e2cfed0e517e21dbb9cf94b3570986c3982ff24eca9cc8c9a6d6593f2981a582966422c66ae77734d7d8f11a84abcea9af31c344c6cf49084b517c225cbbd6ccdd57c35486359ede1f355d5251a449c5316dfbdad0ee87b1379c9c7da9c4dff2ccc2df85de0ace29c6f07109b2f2c1b31487039729b4267afaa7615729332e2d55e3abecf24040c98c9e5214a56967efef1eae2e7f678fa812bfd03fa36ce287ae2e23f51deb10f71bc09911c018466f69e7d686a4ea02b26d2b79345a05b73d6ae27ef9bf5071c529c558b4192766007d6187e87eaf1c22f4a63d77e1efa601b46aa571aff023cdfbb388ef6d275740be31bf3d6d9ee9151c7eef4fdd5828c434eea7624e6063e5f1d7a434f34feea743e68d22a3dcac5911edbd08c60aa07b1ddca2cddfec5a9aab2d805b6b6929306dfb1c84ed5932b80e0e9019770aea667f4b4cbb51c82a01528f9e420f2d925e7c9fb669ac76c2e3453d58afd639df2529e179765ab5554fd61e2dd40ac08d386dc3f28c0370f31d7a3a33bcb6c1868b196db737445ead293f8f27da4241477b469c94166cde7421549f0693cdec3e919c41725b84003e0b2cf7927166c21772b9aa1184c0cd457c313c4162a9131a8363d41e3cbf7a64da223b61d8128cec0197a51fc99996f720e6a864610408713aa172a23d68bcf1fb79aa44e9bd974bd1ed9d394d5c9ca31e067de3c1ad47e88fc5cbc2b678ed90eb54e552f10e4ebcbf393603d631a5cbcdc43853b1f30bfbb5adbec533b05a1ae07811c22486a3d4fbd9613c6b2a0d9e30c266f476df91318745e208ed7ff03c2cb273b37dc5c6e08d7288a50f3465507921f3d21a5a61557e00a8fe0f418912f98e07427502ccabd73b06932d2a46dd49fcb9cfd26dcb4cc384f0bd653113b839340fa2474aa1ede72009881d2b48cb4e9a7cda7360250f04dcad10594b008ec357ba2b24a20d767e709b06669cdfbd5ec8b367ef8727b267488f8b5168afa52f3c09b9e9355a54e7b1ee07d2b3693c9aa347e5857ca661012039f6b39075f2f16abf6463ff0d0b362edb6f07028b3abb388772e123a5c88f8b948d29d2273351dbca0f6010f14a3f4bca34b7de6f48c815445ea9a28b1ac11e7dfbc4ed7b76345717e967305d013ed26cf969ee02abe29aa5fd79d40a2d0c49bae774c7e06df7c8b137db10a44d8886dd72915981af33c7ce7d061d15d46520aab0d0c28c64d532caa0df14f83ac32d7995157b70d796d94438ed27f06481af2208a60fc8f1b6249e3c0647dc95097bff90c9bfb62531fcad1ebe308f95e52f13ddf0c287d9182dcc78ed6e16c1ed1dd287f76e5735eb0ce7641186fd0d44a468583f6a2f0893c2c9759434879aab2d2512cd44968b18428a72daede35b11f04ced4382cd2b8b8f590b93918a14acdb64629f095287d7a5f41df5bba9c17a4000d582647352e2cfa73e04993a5f99d3aeff6b1949ac6a0194b4f1533fa7cbccbf20c73c48cea8edd4870a1bc2eb9df7cc5e5a35d97b2699d09fc2dd7278fc3f002ae1166fb5590c2e33f03db32dee46370bed1255ca7c56f9f26a022f50d1710967ce0846ccf901b8ccd848b2f716a0b5d0b8298803c9d58894acd91bb6fd2ecd4b62bb555a93fb668f703c5e8ddc2693b6128a53f31a61d27ce2ebe09ac67d72974066f2d9696095af39eb3f5463bc014ea0af22eb91b84cb0c29018ba7f7e7a285476af081d68e08deab652a91131bedf2d61400e1c79c6b6de23d71dcdf0000d4caab7908daf1ae2cee593bc644b8d9104c9d5cbbff4ff4cf205f0e94f9eb2a3ebee15a0922100943e560f10e87f3f048a6f7e66ad54dd7e47e6f7023468f806583147bcde17bcc1e2858d7f917a9569fd9c635864d53b30e235655a0a250a835d95c34dc454059b343673d50dbbf5e440490973e0dd70b10a1a3cd9eeadac838c27a2e5634968bdc0c53087991272fa86952ef6b0086603b47421285463087c42c8421b0f8a8dc9b7709a54ac3f73ce28e927efb30f1dbfb7b6bf268b1602274608eb82914afeaafeb043c3d034c62c74e8f75b5b85a49a3b1797e62854db7299dfb7064586c1ffe6c793cb95b19f3fb0e916f63a3eca0b34e7953925fb2deedd9a12b87d7ba502ed10788e707e837972d62763f06ba48ed1e89966c48fd2c39f7a7e4532a135a9c20640d7ef6e36eb377d3bc7371e974256e44a905bba4e4bcc57ba2cf6e51c491afc43060993d00c30cb40bd9106b654866445e130f98d03085ff36980c05370e5d8fdef0bc2a6dffec2a93632c5c2e01d288ba43ee8f01a9401a4b84cc6bfddb34b8035466d097e11df464543f16235e82bd6d501decef397b724c8e9286a7a20ebeaec2215f0f56ab394c6542bebaa1d0f5e8b3ae59aeee3cbff0557a21e53883d606e49aa57d93eb469bc1e75966a25471d23ff28badf0152af2616fa02c7ad0a305f6987ac7f71e1a05a9a9205024f928b0fa3026fa28639599e07796481c82b89c5d78c7b5186768609d11d5701d2f9ce1141e3a1e818520ac3c598dd73aba39a0092cfe2c9827b84cc26e1098c26415b49d470c754d276494173654e4346a6221c6ef5c95f8c04c2210e2ee24592093bc9b6720aff2710f0eba2c40919513da6a9f062fa2e1b2e7d424ec9dc9d69eca9e398f26f62f02c5c5c576e9509e95b6f97c072766dc74e0b3a69136ddec7c8354460abd7b5f2f91138e76db0763cc98f7e106f974af10f6f0f9ec1cfd7fb4191e37bef33c92e5f39d3e0933983029059c572cb7a7c1ef2740112602695fa7d0d6279c3bafc60d593ab3094b335c207e4066fec0c1430987a0c3d81eb6bdf83045b8a530501b2a8065c6381fded48ee0f7b4c89bb883f0a5f012a17a0d18a44039205c616235d4b15c10dbfada916c903d0c7962a18f93a9d8521c797518963035a94b8f0cbfedc281cfcc8953cf4dd81fb253e8eff8bb2d809b998b8277852f14d2e0ee64fb4b1a3be2faa60081c121cc72f3d633ca1231a5deccf847a41f88446a3812db59ce11a578685b6284a7ba5c7eb8c531cc6f17b3688f3423f655c8740ab465efcda38b6c5a1069fbea28401cc6b7d2d56e033cd630277890db72eb9912610cb1659618470619d45d080bf00dafdf496398190f20f15347287a0b1987df8d282ab4d714e2cff486539f94ecc660669b7ea9f0dc8987d6e33bb67e658b83fa351fdae15694ee62731f66a57e0fb112db2af727ef32011bce2c5a65299532c0ec5c5779650260c4677d7dc94b60ffae53e2970e83dbedb43a12d4300d01fa25ee88b7c04102450c5f8a40f4f9c29731c7257ae66ec31dc18cbddcdf6990cdba57029664db457f03912c21dae4d7b0fcd8fc674ce98a6531801376f02117926549d36d0e132471c25dc4e98cc3efb7b2a7c7e5bd6caea82f6af66eb927df679d0c9ff666abb37ed411bb707e01af699b7f3aecb161c08e653bd9387fba63674fd6d0dd4841c1e5d803e5fe0347da31e426b871f4b739e22554f941c3c551aa925e489bb737ae8cb8348f21bbe026c4ceb341ad7a737d572637ef2a3ae2b110e6d924c04dd3a02599bda6c3af534bbe2f678c6c842a547f9029d2693c4655ef5f5e2ced55a887e42cf3189cdde9174800def95f723e99724faed24c28ad5c7edb797fa66af714a836c31ffb06e1b706a82d38534af2ef01c94afbde33b5513b1d94b530257dc83d1771a8340ddc4aef4568a440e69da02246494aa6316ab1c4b4deab414c703b676990ff57134e8d36183dad73262474b9664159dc418fdec9eb7be9292338a5d5dfced843045af427cf0aeb672b1732a11e70027750ce77390f48afa8e476081a45acf9c42ab68642cbafb93469203351d365e7ba965712769444178eddec2d718f2d6ea30b76a8774a8f590cc063bf2db6fd49733d9821b8d50e3795dbe8f82b928f6bf3e9eb2a051c2e10a9f90989b595a02e5fb284787cdd146a0713e720c0bcfdac41b6626f61dc989aed0f954141ca1f77b9123e6f5c3333580cd4da776509208da7bb6b6381ef2607298d2440c9eb64dda16eb44afcd9d864e4e91b86d6d348fe2f640a8cc29b24a549b607586922af78b9c4705a44d7ff73f14ac334c8514706d76eb8538026374ab1ca9c84c93871cf7d01efc8ae7d930eb112d0f38ca5e2c6ea9f1833e24c00026cccb0b132384cad4c5eabf0c9934e6452727f24e71e5eadb0b9d44b1cd0ae010c23613d2e05cca6df6b278e780d76888cb2de9e4b1625db43b13ea244db0f18dbc959603988a61a7a20242a5b18683452c0661067477a728d6af57f1d28cdeb64ff6a14598548bc3efecce91e55bfe6c5e316b6ce9041029329a839a70a4962d4422951db153492ea1f53dc2f56c0cbf1d64740e9f9b943154f6390da0ba1257c128a335022e67fe617537fb3d4d2029ec3401b59a888618a2e77c557abf9f4ef887a58d0bc6eb0437ea5de8c46af666218cff93cf90b0dee1842e60d542851f2ac492ccf3b3a6f84be7ea1562ae095455ffe96ad41b447a298bcffc46468e8ce8e4a2ed47cf05fa5439aab5eefdc30ab15df99b0cd6e87b5a066c94bf8bcb56eb2165622259888455bb854d1589b4e4ecac9542a79e698348082ddc3a036db63d7461273f265458c6c417db6e0a73bf89cce026a638e98da0085d5a120b4d0d54cfbf6a29bd55b62afd86f89e6120872426791e0273c5b775e3e5add85c868eaa84f87fe2873488fb39ba3ebd082f1bebbcc28adf4fe7b46d18ab5ea8068c4e51781447e0ae9bc8d12ef18fcb0caaeb048e856e41e84fa9c7d2bcfc39b4ed1caa518d40cdf16800afa5555e77a968d7590b7e15197d165ae3c84e6223a90801af67b422119086d380a88523c45186fa1bd6ff71fc444c96e0749f2fa4f6ab98e889ce85958c7be7f2ab643198031c7ce84f55cf10b7d75b99c6fd20b6d35befda4a6bc9bad8383a0cbec49e40fa0eb14f02da44d97e88b5d25aa43f78b8ce6c079068cf6c85270d959b6b7246e2857085b0022aec078145eb89dd938426ad6817c7074f5b0350257c5d4f20fb025664073683fcf4d22550c27de7c08e0e9437232099ecc06e2b666aab7484c3eb5dbf6ebfb87cfdd5644be16039528be42034823aa7b16d183333b766d736f692344717afdd51d27c3667c594b7f071804f550bde9c6f8364641c9dbef2412f0d427550d91ca6598cb1f9926e41ed0d57537be698286cb35bf8fdf284238f96b688f274778cc7a565bb6d784dae6a8c3c0441b1f7e2292a3e0ef351f112fdf8473d48abd82f51c76e284e9223a01c43984b68a8d8deda0010e371b71b98f30bb036c2a491f4a8bb1894cefa868b27768648d5de7e3c5858fd4aaa76f097b13486eb788494b59fda6fc9a433d13f9379de2150f95fe2d094adad8c423110997b0f0dc6ae973c70c152ad92d8f0bcbd4439a728cbe97e57775c6e1d5523be571c4fd54b9cf4db80d970c35c60ac2cf8d1038cfa5d01e1d0b2c5f3b5473aaaaaafb8ceb284c0c6fbabe94c60e2616bf99066b950ee0cc60332dbfc55076629986a700a18e75b81fd8df306e0045ac6b787692eecf3f292f30c21cda15db3000d6eff3c7dd0f6ecd6367285168d105e9dead007064a956af833ac085f5b6128b9d2f1bd52d979cecbe7c7692e0939a22de2be14a89d4bd7e539418e26ecb5acb3376c94838047ead63dc521f06cc6bf596df71b4e706b71cdde55447676929cdf1ce4d0de6372ec539e9a54f67b0918400a9f86dd9017f957ca75672a2d06b2f2adf736a22e7af5ef39beb4f29ddb9c0430babbc3105acb4a832f534092ed1f7c31193d00d731516268a7a5c12fe3c6163af9805b0b77ae9cc26fab0d813c73d4fbb1c93e15ff95e862042fe771a9c387f0c128216de1c286fcd06b50d7efda0ffb334ebde37e07a61bcba1571c22b804f30c48e855ea589c707a00c86343afab58c26cd66004b27ed872ae582fb691de4853e49e6966c1bd6ba34d1ea7b7e3fad281a13ad003e2be241b2797aa136584f69113dce9bc8ccbe4db47bb1511475082b6f0df14bba2d71be4ed79ccc564638053e31714304a59d295b6afc122504bb85b301b9f95322c548dcae53e6c4df96ff6b6a34c2fa09536bc1b8b59cb29f80683c0ebd264288b9be0a6bb32998fa8b508c0191fe2af4fbe3f55f0dd259e65f6cdd4b158b1075a421fb2dcc2b6cd4165bc55ae4b0355c9c0eb889d543b7cacdcf1cbd648877a24b6938345b34ecc70a236bd5bdf6ff762ddeb00f0db2da479a8d81cd22037d7ae261a830c84ff7eddba84920254e1836cc4fb713b10b41a998fcff951fb98ea92180451ee3ce66cdf24b2004ef39b3f5938eb33907b00c818d8a6a230d6e552bd91fe95bc4ba7980f6fea20a77da73f903077299ec4a785fec26ce54465b8b4a4d97fb35120e397c0847dd76e5ac990823fadeadd5e56ba32448e7691a3646d52993264739e1f2ba8e8bb0c80d14f1adec154c4ec437590fc7ef8e4369a5233a0065dd5cba73e0185950c797d64ac8725bebe91269d7f32ab52579a41394c7af265d239aadff9cce81d703ea29d263d39cb1a611eaa2979385f92a37225464d83b29682b5fc90c388be6667a4c4222080cb445c8d90ac91ce72eb05839bb88396c70970592d83ba0e83b4b616c7ffab079dfb88b563ae3ac79fde343b544c0cbd16ef57b80b1515e9aea9ded3e6bdf1082e0d6cb885ff22cf1cab9edde11e3e374bbed0dfd6c4895c22e115bd3ed97de63518ca135f57241e6430dde1f785ad7ba6c2d5381f55056c9a491cd02dcab0ee1948ea58428be060d2386cbbb03ccbc9d42f573646f4ab49d46b8d044a5e301e5097f88bb364534bd8d5c5333559436e239a9e2b3e976886a4ee681e91a8d6b72e052c9e7da0b6d9e9c25c0d312126889345239d632564a3530c3b293c6aac660a81c7b839deab0f938b869d28fa05c4d1411c40a9a245abac65ec6ea2512ec837ad91dfcb770080662c5fb7429781c1ca593fc5eeaea62e9f2d63dbe51d546d0af84b83c1e8e03a7f068c9ddf154da5dfbb2698ba507e870b6a1bd71109b95c717303abcd65f01de62b1aeb2212ee55636b1e45c57006082c14827869ca624017dd55bb984f632ab5c78ad802d5f67089cb332a387d58a7628591415bd687a11cb10ff1b15bd02fd8c4cde5caadddc463d44cc9bf4c503a8f8b3c2a6de56da93d24e301e5669d85fbec02868dff188664fea7867e3dc16ca46b82abbaf051b608ce9636bfe02a7e69330be391ff20fb280d40a4b17c070b15f4c6a4aa47321c53425dffe3880afea4d82764d83d32c506000b3395ea8361d211592188bc74fa4330dfd59b7067caca326eb3c6800610c4a6c002899dc8914727834517c3e730e20c2f8641cb78b8e116ab14103b826b408058d1402759e2ed773026ba551716cc3cfebfbaa12be17c3f3a4d559c19acc3057cdf56494e2dc46809d27f1feac795441c178a84ce1dd2c7b0a313ee5b47996b2b59c7de0fbe981aaf866890a9b6a31a1e74269fd967bd007ee4480931feb2d04ec5720568d3152c4d56e6a3b061e1c3a65274b56a805d262248c661cfe418795c29ebb0d18aab18d42ab3db80159aa44be35b03e4856a6195dd69a36a57a2d252b835a90dc13680f63bacc2b4bf0e22c230ff91fab56e565d0704e21a4efee72a0faebb566e910137c3f2bac81c20ef16ecb2dbf2394afb658a4cee43825f54df5ee8a10c6279dfb32ef85d9473b0f83d51e69195e2e7f2b6b473a9960d74d3cc5e540e02a0d199198fbcc7536a2e4e1a0076c2d610c78eefa253f425a4ed19c42fc37c4afcf522504adc829a4bcf29cb655c2058e53da3b2d0c6ec5bdf0aea2ae7c50c9e69201f240d89aa1219e41be44b896a90585583e3ae6f910fe463658b0a7af0d1f9f78385fd98870af52cb42786dbe87d697df8a73c68c8bb6915213cbd0e57e31f69dece38bd68357d89c14994f00a013fffa91af5df83460ce598a0bb5732d42627f815d4db6acc6e77d63d8fb1c50e6676e0e8bee1a554ae14a18058f4f64bbad3ea2d89217f98ec48136498254e867d4791c4a54ae2243dfc035d0772a4ac9853b4af7079f73e1c4d27b320c6719f0f0a38ad1660dc32cc2aaa8a4be1dc703f9ac176ee3dc036334722a1d7cc77fab62a6946ca6d7645e2cc9d6ad5891e08c84fbdbf3fad2dd57d35d00dddd901e88f9024883beb84d15375ab77fb122a2662bcf96c5cff2f9b81b50246beadc7aa2e8060a9bbf88e3ab26eea83d18fb5cac22c36ce5537b65350e60db4e3e096f8c7fe13153c953289637d6501063a0a05b10f16f4b1f0ca9aed808500d21771c88cc887d364719de93750ecbdd819e604cbc77755f95e82fd0f74579511f6c48fc9d829359ff37174edb14ad4220420ad87fd1e11dacce06d69a26a2649e0bf4d4faf05afd5dcfca7c0871770af8624290e8b67fbc2416c51bdf02119b3b754f2bfd754cdde38c767ee0684db005152286eb99cd98c259282ed3a0fcb81df2121bd416f3322ad3ce09f35641a19945936c3b60dbac1e462937c9a5d63c9a8a81b1f4d7f9059c6b7ef3f82d545a40ecef694e08548f77a773257d6c423f937fecd01726c82e81d06f187f4ac7dace245e92db0513512360ef291999973b9a2f070825c794d28e75ca12de271a5afddcdf73911e87a129842a243372867106c2e1320ca0a53ca71edaa8c78d59359e9cd8a1095c50da577e6938b6dd0ea37d76934f2f143c8fd7abaa817b4bb30d7c053045b8ca985b698dcfac81d163da0961be7663ad987f1f2092a8919a132b9cdcd2e96012c7c0a17cbb83bb66f7ad88de26471a0933bf1b5175455538e4ea4eb1afb20f2ec33a31e9105fe9d925aca8afe6c4cae62b18438ee85dc203317f08e552307737f3fa25207e582922e5c53c069614db82e0ff11c13adeae0810f0fbc3c2810f04963feff4561e9c2647b8569614bbb7c55daf74e895f454bba0367c4a4143c7a29d2f0fbde875669b319dbe36e49185b3de1345f43d776a0056f1d98f477a488e4481590e29fbab70c0c2ffc73dd8ab564f94dadcfc8ceb532a9fe00e6f8e44057de47eae6d3aef346c8f584cbe5c919a8ada325e51f15fdee1ac45eff1fd21c15003f9254c7ad351dbb3e1f8d6697f0e2fa9a5d9907bae49d74c2a95173cb56aa211905062603776420d2e4d7550af691d6bdd244a6d9be90a8cd6b8a9ed7c140a01855a8bc9a06b7996b43af0ff7622720b855494f88447a5bb9805ea61f9e3a691179b43bc6eec169f994c9f21f6153649fb76427df4d5b1bdb5f02741ea35e20a489c68a614efed81fef50dcbf12a99f81925dbd4f7d9d2e8dab04a86bf843103f3178ac310147cc1bd45035b5da0dd0008717249e629a294f1a275ed372f4919f8e51ec9288c8a43bef7927455bfdec5b04c8f89856d778cda3ecaf710d66de0ce8a8747fc7ffd6003b14357de9b58a147e430a8ac5a42626b76706e2190482f898afb230f4859d190cd0ab686140e82f6b8c69a88f8cd29e85984cf95c817e33e8123b3e97a0f0422423abd890c1a49b404170446024499a7199d68afd9b72e5d205e2e3fe070f4f784d9ba1fa01398715e7a90bde8e4ddd558cc4008cfd801df059b18e9fae5e0c11a9be7f97d6df12613441da6752fad8430961997776514b693f7309fc788b1c6a60673f6723a14dfe8f274501c9e071abe0c0254725a51f847676328d524c69a5430613fb461542793ad7bb0ee4293ff8603504bd93b9cc2b3b2a794631486ba21bb990e571b14a3c610feb769cad3418818f65fe8f2ae662bbacc6d60e29e493ce8cf1aa8e6789182d18a1d6a8065ac3ccd38b20967d96d05af43bbecc68208330f321a68fc756927981a376519be834339ff27ea1fac4939875df8032b2e8d13dd943486ced282ed1e06c7e58e99392be081afd7f97c6271d249aca1175a0d0f9dcc6ab8170ece96f9f3b4d90e3f5c400a660a4fc2b902c7676ae611a5fd090240a5e9da62f141f5dc9bdc5412d74abefd111df492b4680606a0de332071ddc275eec222816c17a4f1dcde7922797c3888e5ccbd340d345dba3a07f298865ce45c27fc63c99a2a7f14b6d73114d17063a2ca2c888e037583bbe70fc1e601521e6633393d471736b0aad1558b98e672194e4cf34aa8b862bf22939e1dafdf0f8c4b708ff2066e0872ca327de0f8c12b1a796e5b838e8bd32e16504d58ac1ec2040c2570c384743bd5ecc1b99583827ef3da1e551153c12244cb2c9ce972a1d1a74194d5660f670aa8e3de15efba4caf4d4165a86af5219e2287c27af6efa2b9dc6634c5598276bb8b3a9988fa069b901eaf44def415ee5ba6a6ad53a4e88d5d750c96f9fba369a91932431177e0525177b4570558bb7426ca8805dbadd4782ebabca28cef02a3f09421d3e6895849a437674cfad573c1fd38c28e3cf7a844a9f5adeda176b32c866e7c4ada0461cbb13afa5083bdb7703a95341cf4d05f8ee5042dda09355988dcefb6e1ffe96243dd24a4b9e9504e78c2b1887614066d8eb4888403786af506cf6876897e8223757070910dea6848e31fd6d85884c50e6d525ea1d57fcdd52d745f76dffe969d69663386f75db44690dde6e6a258ece77b625115be83c00cf502f0c95f70592febd13c409d275daf4f5a94cd255b349a8f7039f8f0a7725d2e448fe48ca8cc1c76e5dd492ba3daea9c2f8001e9d2950bed99f227f0127286fa5f70d6eb803f863a767a9424a4a35c94e28ff457c469bb7ae6fb97f7e770daf2843edd67aa7c544231a21e49583dd6f7559033258cc0162f6ede37fe2d9820fda1538701c8232caa9ed5248480dd9d20471ac9da875f6efda9ad5099ab61f358af6888b53c0234bfbb8491f5731961d6f5f89e9a008506830906f0551ebdcbc73688ebbc226ab0f0f19f84813fec0ae10fd276d8001ac7feb2ed6bbda6b5e621559b2e965b9ae9815320ea450a6d419bb6317072e152cd59cbc3e777e95307aa35e0e5ae30d27bba462d9bd8a17d0254e3b651149bb33c47c3b808bf0a68d7a33299249f4d0d136bab482faf65d51efa432510a123fbeccc757289619299c0534fa020c9436f094759877a2e7d1bf8805e117b26bb10408e2ee9b5287ad490b7c6dcbc94b78fe1bd0cbf5f9b547dd9779be41509fdc855e2ce27aa795a088d8306d6bc1b8398ef637326e42da5529d4f0868362ad3a43ffa4ea9a5b027df83b45ad6b833cf9b1c0b9e424cf204615c8d38ede64b72b4d2418b0a483b187e76a5c54362c128861c4a79fc6cd60801a53fb190ff3807b3d4336cdbedd0851e9350736d6617265ca4e29e182818efd1d7e369e8a4a4013e3598ac48626390bab3c07a93c81f4028631ca936f0a2d7fb34ada423a8ffaae057d9fe84735f527ef3b2ce996ede151c4cc3797f0cad113a1abe9ce9083b76bc75c8f86b03c6389e4f4d2594d35c7896c8cfef0a5e62905cdade03cd1a12cc83ffc05429ab4db034dbaea92416cc894799ab5fe49b46bdccfe91cac6dfc1b86d7578429b64d0395c4a2f32b86157191fbe8bcf370ab49af8291e0beda2e50e06dbc995e7fe423ad135f650396174afaa28f763bec40c6a93190a9ad21095a9e76a04985c4cc42cc065abcfe16bcd6319396d86d711d8752cd3a252af6605c656fadeb681b4eee2dca26e364252111b9aa3a4da61d849e6b07940f98b01a0b8b9369357542a65d773d9d9185048c0ab78b5d77d92fef20a5a0e1dc392ea760042c5688078b8363193328ea177df4602cfd11f521391621128b2a331c270bc56f016f1829ec7ed0707ef0f42bafd084af7203d9cde52e7297eb4d1afda45475521235dec823143b28a63fa9caea89903e18d1f44d03bc6e9d01e056bfd3e762b188da04c809110ecd101f8ee45f0539910658c43daccb11b77ccaa11faa67e584b5d6fd24e613ea1455133c16336d030cad0f4624624fe08e0b885f5900ad6da7d793f5604d3792c70285aa23667e1066460410063b8a471e0a4248656af9124c43342c52c7ac9ce9c90177302b1528b29588e58ad36f8289050adc97c9a759a28477bd4d08dbc16ce3c9c7c0dc669f715a88e7d39f84335a2a1ababba6bcf330a0f0a937fbafb8b1f0affb3da280d20532109b358a0bf4745d510832d90655a670c77a40d1c28e15660bded663ab91a535d8360237cc510e378b9435edf09deb5d17f642871cb9e2d4238e4eb097d86455792caa2e15725c35a327e2b5dad46ccddd3d08efd542cb2ac9e607adac7466a93c56ac1e426df735f10155d451a62d77f8588bfb191ee744dd52ac271afce6af1dbd58ccb0f4197251344892627f8a2e1d7612028aa195525c283e801f7a30751ac612ec33e013c1601eeb066223d1a5ce8c4ae55ba96d1ca3fd90c604c7086f916a613d7cb9804eed77b6ef7e85c94a8b39ab38e2360687f1ef7c67dd0279da2e839fd2e14383c632f6498d53880933e7dd1267d4c210f34d9ff3d6376ed0d0ca3bff524f352caf51b827e8d6775267fe7c799b066af9d267996d521cab8039476b57752bae90d275e8e3e28f10a52dddf23351fef455dbf2535efd4ec07dd4dd1aee73c602c05a28754c9f1abce743dd43a4d7a26eb42ac536b992a7d1e501d415f3233e54185d65f52aee7098173a3268c24bac1fc851c8c87860e30d1bdb6012665de7f32cf25d9fc679dcebeed55154e71581a898d5c09bbee98c8632459be191354c9991c353231f72d1b166566ff754b2431f1494be329172db7f44e9cf0c78219fdcd921f772d486f80fe3e45f9d7ad39bb8d53c6a6726d10d6bf5f5968f2edb8c9a718432fbc2638b4ad33f8c5da31675fd83639a57ca5422157ae00a4a1ab7b746845b5b6891509be1b34ab7ebc8b5f99982b9f3583ff42688f3a6cd1412de5cfe155c02cea900a4f3d0989f964eb6cdcf79811968cafdab3759273c0f3526c5fee913cafe60c593f0126fe6b8e07460cf8544b2ca5196c529f9e208c37949fc74364eff81c2c7fbd73c9afb8738e6c4de36b675a7070b49c4b2274fd80aca398d492b3dfbd72c2f1fcd2998245823ae15f7e9e95d25f27eeb0332ca6ba0c827b5adc4d62d8a1758fb37fc62d323c451c7f2c98aad9ea4dc82d1d8d49af68e835979aa106b2d7e7065df666db2f3ecad04c1e6c86215f49247fa1363f0ec44cfeeba325354e4c5b22f0b835a92a5ea21892449e8882fc0e93ba0250bd47afe8a311533149fd1cb4ca2c85da5ea35c0cb1a67eb02f04bc39407bb6b2ffe414cb63b1dec8314f81d4af413b2f12166fb3125c74316ee7bc16410830c5491545b280237e2b9e665ebdd6020c73c972e83b6b59da2ed0e284bbf46e254900740983b0080d3a4e739b0742217a32ee132da023075b174714c7e2acb79d3325ba7c23b4838cce0ba8108b6d78db7ae5e42ab1a4e49b68146c665d3c2ec40a6eff2e6458ce8c8d9b023889066465973e502a09ec056671849840ac3d5047ba30742bf5e3d019646aa582b7f1ab3b35623db447f8d5acb5ceaacb9d870582f871a1862459b54982e2b024ba4946f7b63951de2fadf880cbc25d889faa9437005dbbca6dde5446d4d6a433610b86d5176644541a892afb12b3e8950ca9a707bf591e611ad88eb8c4c0fd1e4b3f69a3ccac1c6031202ca899ac5abad59bb13821f09e11775cf59b23d94ff49a021a8785322ed171a84a8d3684038cf6dee73768965402524dc5924a7291916d5abd55d98407218b44ff738dfb688488ac595f84be4acd92e7486af257ad580dabd7106870b764ae642a127e61b3800e8bf72ae059c4d5a33fdc72792649429f8c7936057cdea8f0c3a7a5aa6e04661335fa4933165904e8f05c04330f805d388b02fef874492c2b85d7e80516ab66af26a31243633c1aa4721f39f90e1c435f32343b3f6d762b4620333192c1ea3e38208658f0e8e50e0af8194a36fcf37b5dd11aaf6d748518e2bf4748cafe06c1a1d2e6c4f742fdf726aaafab8e4b8eae38babfbe622d16ce92bfa86257da6e4d8b6daf6b8d8a38f3bcdc12a87ed637fa46e83fdae775312a23ce242d1db0cf1ea512f133bedf18430c23dd9e22a8d3d1c743b7f3d9799782c62a55f8c0a296ddaa3ccb9d1086ae55f1c538f14d3dd22b066799d7168dfa556bbf484d5f9616327fd2b08789b0a46de269afd09a4ec7b78aa89324532c3dd2a87823b393a63af4d30499b08831155bb8b4ebb8b829a5b961b72ce5a489773fa801d0f8fa3e429f16a11a3ce4803247f8a50520e63abb75410ce02db8cda376726096186c9f929d41b83e4f2b3f1dd33ecd26e48f5d426faa05c566477f65f1b8aa3c1c9a32f02062f72b1d32fdef397fa5473ae8e0009ad07bb2ce690c47bbac300203cc489a1233fcb868c70a5eb13538f9e18750270d1f3053e6cda4486771e0ce48e9e65f08a0ec301bed4e4b629ae4b31fc7d010f2981fecd29cbae5d3f59ebbc86784e8b73ff3642d8bda218ebb73e5d1d2228bd93aec2a63540eac8546a0ab8720b7737716d518e06589142c8d7b2149c3ad59b3bffc0eb6712831d0aaf3083f33bf9366df7c70f2f87fa483928b3d8fd36d1cb4a66d0d62db1be276e236e83eaecf32dd61d41c3ef3556473bf9fe32f1023b03468744d8803233f71fea812086fe5173ee7ea618a07fb99cb2e72b7caf7fdf84f34fc26760e0ef5f392949502f90e992bf4347d3336d4e62073bb1a3ff85a0112d5b6e5ef64ae4cb428b90d75d1828dca5dabd09f5cd0f6a5ea0b02888e6156dfa1de917ef7a5dfc6f91bff62200cef8189326e2568bf8894ba92c843949d5a76c37cf1757477afd8b8c2c5c633b239dab141cec760118c255208b6446c719a9891b052f5e9d5b6fbc43e4bc53a6c88c34001dfe3b83550ce0797379ad22a7b490c77874700efac3ef55dc07ea0deccff8133fc0a882cf2e8b80cd868c60371cba683a53a5ee4d2d8154a242f5df3341e856cb31d091b7df417c4017684fe47d25c1bba77dfebf952f2e0c21db69be22ad56ca715a94851c7abe281844e13ef8318a028dd9cae3ade14e5bfbc168dfc6d577e22ae5b8bd294168e6bd25459f9fd8e1de6296c62486853ed534e6b980b291ce35c14a06abc60a3a0542b289640d918f4e3911263eb08313f51ad32075f380b0100e116e0a805ffcd37ad7a813686adea26d54481e959b2af92401be213938c346aa81f47dc16d55101d724091c73a1674b0d033718ae2a78f7a70f035bada87a0cb0ee8cfd9a203896ac3064652f450996f8274bd9ed8a18c91e6dee3910e79456de5133c84e09ba6612591417ae7fd7280023eb019a98c24eeb666c5ff8aef2ed998688e808c8bb8b1ddb13953c185c01cd421d81814f31f65e1d82331f783a92124c740e79631dd57b6f9651d101136056254420ccb81de56a151ad43150425c2983a1246944e409dcba68e53bc162f4c75098e25c910857e81f1cc1c175e0b90a547cc29a68a055f0b187d5043b26fd89800fa621a61100eb11d9019f9609f9d064e95f84f95445f925ec82a0a7400f2398623560f8a542266724b10138b0235be171764eee42586a24b752d502efc902320a351f3d0ada6d9e6fa811b2e95345d0120b6638bdf55f939557aad980db6ba381da4cc2a13ef127403a52ee4d83c7bbc0408230444fa781f6c3da04debab89ec7b689018bc1304be7c7ead6e236061fb63258de36aaa00de3c8e9af6cc6cffe2840a223764ea0042a4d177a0b03a8bdc6af87813864a9a60c64099a9d843171a6a065f541b216b2bdbd0553803bcc74df5f955be3587d9ce7463023ca6870f4671330d1535d65ee9a2d6570375b3c6906a1a1b366a6a06006aea0f9e63daa05cdd4d99cc9d15eff185c48b67f12e5792af6decb3711b56ff7bed48cd087ae799a7bae3b91205782c165fbba6c7e14ddab5012d35f3b01bb9b1cfe91cc276dc0b645135ee3b4027fdd42e8a8148155f70c0c7ac46d5fd55c79380132b98dd6a6aa35902411ed6ed4104aa4a77d32fb4846a05dc6d3c7f2dbaa57a4911c4e1ebabd001db0937972c069f5a3309e63bbbd148994c8694efde2b3c944f58d9d2982bbbd83f17c6ef1dd4a383c6b2c5d1cdbbf952c2b3e0b38f4a029171fbbbc25246f941f9656efeaf389678e1b5ccac6db4011449b5a7e1632a77ea67887f7caa469f55b944fa3511def522a4a5af0eb2abb2c41e655138e26096203c842b1b4c7cde7620c576d7d49d806c6b5ac84e7f96cc27090c83316915f5920bfe9ee866f679b214a82a40f4e9cf00b3b28bab91e28fd411f89dc4213ac629ff87c8627b1e1c447f443821f77d07cdb3efac6764f228a69473037320b43aa6fc9fb3bcc01b03edae4a34f527a09fab799faff1d19f61ec7e5935637b20bcfdea6b80d6b27d5cd1d836a1e5813c13a19cf79f07e8625943207f13fd510c890eefac174fadece54c6d76c66c669af7264d41449e147bc5907555d947923ee4051f2859f5c37ce1730e58b6e1e006f766761ff16016e66afc28a42e13ef27ad3ad02dc2ec1c194646a18bfdceba89ec85eb24882875b382b14137099afe47f87ebf3f65fcd9be1729dfd210f34643992435d30094d85d822941a9d833df0963ace1b680a0367329c7dd18be765cf739eab13e50da269f8cc170e1b32d4d782fe6aef93b35ad873a22dfef8d05def8b03704e3c483602746f20a00ef4537c140d2dcd29cc8283c4e74296d3e1770114afc5057b486ea432082788cd9f915c1efefe18354999962ce96f79810f89dd42804aa2803c939bcd9ec81986045e585e5a521bec0ee7e0ae9e48737987dec0ca9d35e15e6565a61dd34d8da0cd5744cd4f32f17c3a7c1f4cb884e8e4c371c68b973f70e84e715396268c4f6fc509622db16fdea6375c85b544a41db6cd0a4f9ffa5a2ab92980f632a141eee2a29ae755053962b79857966d666b61296ce5e2ee4bfa0fceb8452f6e43968b4e96867593a53406ada3acf04a3587f7bba4211dcff1ef942e65549c920aa02ddea7853c1818499e8c139b2d8d18f529ef4cda518db5f2d70b9ba71aa9aa6e4fd0e75f43b57ec75e3f310dfab47943d8bb7f976cd4ead65a77b748707d16e75d8542c8079a187a824954938a05f0fa216db964871023f6c0925998cb4973939e8abb26549d17b3dbce497500e8db6980538ad2430b2b227b5c2a3b8336d3484ff2c231826cf1ce9d4ced7825a01e988ee5644535720b302911fdcd97733e8437660361d2161398226f0c22582562c1c43e37dd9df6edbcba26799f6d5d237fc7cf167d326611cddd84a5e47d78d0b5fbaac964426e9d10d2180331f14e4c2d3e3a1ed3ec56071d0e50f682ac73ec0ad9894f941ac6938909376798d611ca5195948ee98c18422169029f04a54c9a46e7a29fbcd960808e91832ddd549563cf27f40967504a899d90cb0c3f8e1ae5358e36e8195a812765f6d70de39c9d21943a303ca4b32e7866a63de66840d8f223f542d84da616a0a60761d828adcfe542b598c12654c2191d2de185a441d1b295395237e83466c90ed0ec2d9ec63da536e880a3e7aaa71045c5939630d6dfc56993dbaacd6587a8d2b20e3361d9216cebafeab045d616ba17eb17c3b09a6d94c281ad69d43bfe0b843c5ccdfed2961175d6027d77125530e8f9fd9ebbb53cd6d808e96ea2c766f182c0881c8bf469b16c85a1bb7015d5a762cae8dbf5770b17d8c498a25531abb796f5bb99c244d1334bb7ea6c9c2ba37a6d7872710f037caa6842203f28de32101058ac7cd3b04355a2ead0fe6b88d470b3a5e3f6acba8b4c10a2310c874919309b9846d924634e5e788a0a142036752fbade81fe7e4ccc533011040e0fe6c57c6fcd1cac8a6f47b3232bd56fdd87fe752bc87cf2f50258645bedd570f086a9ee90ce7ebd8cda4c0432481e6330d4496bdcc3e4ac85170683ef4ed743c2d073a79a54251bae3941b75d4037cfa026ffd6ccba312f6bbf42e8f95d2c9a68708edff150b2681fdc2537ac8e82e26b2317fb35d129a5f61ab7c6a4d62d806b0ac3fc18ad06eefe4c267107b8452823084226b7d486543d462493c2de36d001b0f8d0f7663b0e775e330b85d9615d170946b8a29da84dcc78aa0ac87b97b3e1bf1fbdcecf98ae8f41db19577dbba859acd28997b9a25503a36c28ac86b1f079f9aac218d7960871578b0ae98a08319329f2cf74062da7b507c3ab326fc26fb7c41d8b9ef962ee1ed183b4e6ae72db74a4e7e11dab402813f836e8991c8df580572bcee8ee0ac2f74f733918d0f6c9a22100762778abc448bcd3542b162a78430c8bd2aa5c9c1b98e441d6b602b02d64f385d25b392ea7a5c1fa29b7abdceb0014fafbe1d46ba926becddb530d8954015f42f3e4cc92bcb5694669435354b112470d1aacb65fde50ced7d16284f63bb52a6056eda17a31eb8293bc85131e8c394de02da6fab7261d5b2031a4b4eb51c1d65cf528821481e7b979fc73e1d517cb2da01572f143076c874495f8ae61f0be17914283ebfe2a033b4444a8b38e7577acb4a782fd29c156bde34755f98104d822ec6ab945361ccb049fb6181d7b2a191581c622b45fdfda62607778dcb72b012814c61ada7ea3f10b8d181e35d18e9e53fc2ff451a0191b67abb39fdc8f20834af63078f81f75ec5b191f4aeccad0bc2cda9fa7dcc6ab1239f155cd57606ec87a6424c79f5c56c115661122b65ecc0557ae221445b6bb482a8ceb0d15b394eb0e959d7e16a1804dfac08194ee91634e6aab3f105c07af1bf4cb3abbaaef931a1525da2d1854bcc7142c1872225dfc32f461b426e4b8a6587e0662e24b18b9ab44df5c52b02724031b4cde63d7ec08080a24f06131cfe7b3a9d2cbb52a5dfa74cd03460dc6c78919b3d02c8c4339f4ce29a661b0f54e9b67cac80682c500b77e4c6e1f468104aa135b06254858c763eada4c02c24d0d22819234efb743f6ed1dbf68d2b2262a730bcce2b626966b05ea5586887190b08cbe7d8681d4c8cb20eb1e29332871ed91ee6a8a0953250c494e4ea6626c960ec240f9726d97169bb9f2667382e762e9b37fdbf0542fe47b397254115501956074fb0164abab6a11dbffb6e202f1c671a04d733462087f27e831f5d0c82084279f06fcfaad53ee5c736951b86344f93287c0ba71bad28e8175059b5004e358edfd41944434f3ab8b624db843c8f98a7f061b7c66bfa3c91b9ebe1c46d5469037f1e49be339ade1361f2556bee85d0466e6183027a1e0350e0ee592dbdfaa69ab0b784cc32e2381f784793787bbaa3aa3e52c760643d1ed9cb0ed4c5fdd0e8241fc2b73604a01fdb23f61662e9f6bf045e0094ab1188d266157497f61bfa2e95ca7d1829d3b441c2e1fff4669c5ab64d29a5325d48b4dfad289fb975d038f3b86330083d48da7b4da9320f6919e66860b7191f0cd0cde27432ac9e07c6d735961e36ca295bdc80a89aab0b860ce5ab39f0a5580b3682a7463e12d992a55c2a06227550d29851e06a1f19bed410592c9c4fac1f6e9b39db71d52f32faee41e27b36e954b23fbd90b15c8fa554622bcabffbe4bb9249149b4a8441cec36e8164c594c742c6d6fd57a1c545b5f3f7c8238ddd8e3432ab3542058dfe5b91d2ba84b126b049b6dd1d477062c50e053f40b4293168329e644da0cb35402133c2cdde685ad077b048e17dcbcd425e4c4a9b4c38f8437ce9a2e8bb8b2c07e78df4bd0a8fe4268288e2f7e973df2160539798951d2fed61605e7fc40d610a7bf643c3bd97703c8890d7e8241f1c9236837bfb8e53923643f706c660f96fa545baa3f9c6c69d080b25afe98f36834ce1b4caddc8bc1c6fa7823e867f676cd7aaf183495ea4c3c11dbea499129f98e6d92c0ea39d314e1161b464b479e20413d7a660cc390955cc26194a2d506e65df5437c8bc8372ddb28841cdba1f8acf026763204cf665e188846f4d0903ade77b3f31da8ad1adc1632c2ad02b583f02d730f2815a9862b6dc858b2a132cc53bed3ff87f10f32d12e9736ae01aad8c35d722dbea17c3438e45d173ace828e2a56f1170c066af7f76e1d61ad24912767772165d7e8e6369fbb6701f3f34d8ff71d83e84dbb0e2f0c0715a0a4eb1363fe6fc3587a04a8ee09a9d3e4ef32d52baff8eb04c49746652ac6f2f5829f9236fe6618e0e9c054a53969280e48406595b138f221a1f28523e17880a5237a56bb92e83678cad3cc11622417a165c90893ec2776b9a7bc6a39658d2f6ad57ea98de514c1b8ece4a7af630cdfe7b7f11084c997a28cc5f839c2d120697fbdfef608c13c58abf39f9b20aa85692475c0f5db2923345d66f60e8eea6a06a3e1727f2f2ec260809181914a3e1949a0c1f33e683aaa97524765b79d64ee6737910e157a1de395af6c51b3b827d1d8e439182dd5868908bc85d44486c63bf9cd5ef25457255f5a751676d885d4426ca64a9b0dec0278f69991234f48c0fc5f8b0fe0569a80b82c072c3dc195fe5b032508000873383e377751edea9d56810e02f0e039505516c9ccd09e5a2bccb5a21910ee1f2357e0f41972d6d8ac63589e1ef0a64ed273ed759ac670db92c7bd1fdb46b1385d3474eb612b6f8f070b15cc0f6359954055b226a4f447292a868b62d2ea1a3c8ff0956a28dc212c7278e48d74b92e8f88e6cfd769fe6c0bee2da36a480b376842298a4d4508bc526b743c3da2252ffda33563360651f0a248d4d664fdee2033f70d81cc1a6c52d872ac19af299fdbd9f33bc1823b8a2dbfd141d636d79c8309f363542cc650548b98d15699bc1eca5c57e596f2358dd2bc2bb89e4fb81b62a3ad7748d4200dabdb0c828eca30b23dac0a7e51dfc926ebe77beb70c5e8bbba24899f4476b5d4582357db186bfd1134f6fc35910c7a28d2848da8f8c8bb7bfaf238df870ae1fa68c924899e3c63792e919e74effe0a6fd7b7fc38e0ff62cb4f86c35aee199f525e9606fb07b44c1b6933996b1981667c798c7618d12797e3c5017ab44b737c3c56130279895dd7b3f8688761d5f0c784c5bbba489e9f5ea517837bc30f8009d2c9a56d1bcdff3cc578fa097c05f9840a2be0355f749925c798f1b5755d2225b15c5f52f516df7709f10c6823546182df6a2fbb0ce5e1accf9942d1ab697034eaf136e2f789b0a563b9c2e34fe63ca71c9c956c9f0b91a74239c6ad5d6248982394eef284fe5c61a348bb5c532c8b6186d6d6e044c7f485b1db621de4386ecb532cc053b4c03b98fa56e02ae3e68404cc28afe6a159fab285f251ce375830180f248479662a8489746e4a40e791eeff3017adba3f5bf8ff33195c5cfcb88f6be53aa5225242aa8c2f718e4b339d47a16de971a9f47bc2e1e7f4270024bfd80f00dcff30585e9cb78303b2baeeaab21e79f1eb8f6a0aa90b455df903f353e1c0139dfa391732d83fbc0af72c45769fc1df7c9379bde8977736bc21274a70c49df738fac7884274c68384248c5a8fe8ac632da6e5133d618ba45db0d687a27d40a11b47915283a0af90143fa74092b31c55bcb8986f6c4cecf12bbd1ca6856ea79ce2494366fe79f3bbf1232bcf557edd351ca92be4af9951041e440191740beae3d094b27547ff175c6e1b4ab58f4e25d111bdda973e8cde7254b4384950dec4337b5a1226b099314d85ab1a368d13620b5bf5b04c8bb784b18d866177fb7e9b6e8a483e4f5d03227050fb7a94100ef140fc913e67fdf48cf6016a5161b3f1bc339939fab2147a12b25175fafe6193aec4f149a0a42054a2c52eab010c151dab5b892d42b211a4b3d15bfdf690476a09189d6b86fd15c7594a95ba301c6f29d0077062365048943d3a4b97bb068733e6eb6a0ac819c34a26c373e1fefac0efe07b0ad247ddf86b27c1dcff9ed9d25ed446e47739038cf1d615b0c2753b49ad2a34eee5d44908477e9d6dd737a3f6d80b48edf1cada7ea72738d0b9e3fc9e7c944e1ff99e2fcafcb14c2fd217c32742bd1d34aecc5e30de84eda9f82faf9f297093fc8a48d87672efea7d2e8e7533ea8ae54452dada31deef1cf7717e4487fd287be11c445b1026bd42ff19cd37dd5832519c298198825743e550c4fa35c3cc0c1076bf0ef8af8782757789b9bd85415c0e5882f8b7ff64cdb8af154441c49cb61420132517d46a0234e1ac4e951a576ed43f02b57deadf022944a2fcbd1dfaedcd37ad425fbdb2ff65bbd930b06be4a39a88101b6e8f7fd5aba4d803fa840d6efe2adb367fb54d23fd77d1b1f1bcb9b5640d901e8e63271c96a3407e5c7e6d127299039135d876299f14b1c5a7eb18f7e4ed1600022d5c7d41047131f3ab004108896a1911f623431f8f8a9c28623be1179cb0e1569f1f727edcfe75446e2e591f4ec0bb53c87e10a06cfb53b59681d8f3a567b7fa2a95ef1b2aa8ba85c75ca34e16cc3127d99adc8e0ebe970a25fd14b63ffda97f7e5ace767ea2bb19a4b1df51c810b10a7fc1f15f25c7ab40449272262e83b72ea2569d78aba08e5627af73b8a681e7b7aaced9337aca325af4967608c06c7da74869dcb1c5ee106cdcbd0929850dec7d07d3e210ac68b513ab5608e6cbeaf488eab5ab6ed88d66f85421e75a53bfa50929132b5449e23ccb1edb565b0d2bedf9d2bb9e8d67bde689e4656bd991aa264896aafe33e0b9a71c6a4df73d54118048e6e7210171cbb01ce423d9e071c18075f8a810c5073421e9d3646091f21fc0c462f0bb1615a341872a3c231b6e37dffa7e48922d7db8f471265c7e13ef7bf39a406a6956c065e767f497b74ff9c702b065b0d1c93afcad9576488626fa561bd0e1cfa353ea882ff06699942b568e67864c8946f4932712652ccaabe5600f75fd566c8213a1657c86e8af5b45a76c460887514bf0760637396176bcc67d40b33ba78899005caf3969524e359681f0be50a9e014f8f9c2d111a461b9dd3bccf0e6a30b61ff7a13575e63900398fb86ebc0dacf71c7494bbfba3a440031aaf2a80f22dae2acbb2f022682ba947c1eb8f88950b181ca3657f4ca32590c80cbb6e0946c1c16b8e696aa8238de4131f468a71a5bc57a4f9b044e5592a70b3a8f5317f7e15762f721465463c573e8a46a123909dfa2bde4d4cf2a2ec9bb0257e1e55bccdb106178794505efd4f4fdf0208768ba2eb57189b8c354e0854389d38bdb79a0c2ae25ac4f6ce8fa8a5757e8eb5c29cd64a7c218abaddbd3b0c8a7bc225b64e4deca8725def83222f42af45d756a5d4f0ce908a2698dcd803d9c0b1fd397991e8a9f159baa3a3fb98c82a98e7698a83688d18fb62554376f738e7a1cada75e84f9ba791a5730ab3704f915ddac5d91692717e678c4fd459c9b7b44428b4a7832a3235eec9751f2930138996c778f9aa9976bc98c54b373702a92b838362900f730c5b801c38916205d400255df668287e3ed06897720a649a1a8cf96ed316ebe4a8fbc64098fd7803c1c967e7f55c6e8de987204f25f930b999798583837586ba82805af1aaa33fcf50ef33e788547e43ce218f44557c838054b61a2402a775d73a4d245a76dece80c21b64b92c092085f20ad3b22261ec136224950c9e0a7a46156a251537d98ed3bcf030b1d3d90db0257e37deb24da79f22f424d7ba091c89d5df81b0e71574cbd260f4dc9f3bd7a9ed1d41f668d0c28f89f9470cdb9dce2f653ef02277ab408a21b39ed061d17d6c0e918095dfbcbee8868b397894bc838bc3e012f91a8ce2e5fa465248f5cd4faceccfa13fe6b725bcd95c5c1f9e574b40cff053c7d4d08cf919bb44ed1c61594f822d264c051a9aa08c4a072cec4fa69afbb4a71086d8b010fd26e9cf68e0a383fa7613d723511a0e17099d89bb690387a4489bc406377ecc516810fc033ce92750e72291c3057d7e20ef2668fd8554dd0e1bf5b895e01f0e7f401f962484623bad5b5b3b8c754891661f211bdd768a2aaac329b15ef2e31ab04cfe4271c079ba1f4bf59579cabb7ab6de61bb18eab5f264bba79a172312d501e2d175ec1d3febfba71664b8aada135da0a9176e5f59352ec2f66dcca0e69ae16c537fc3dc5c554b36ac97fa7b54ebb82c3024b5d76428e44a6e3199720ae3ee04d2969cbcd3e82b006c18cfb2e5ff6bd1a5a31e76f9d20e0109778f91c43dae81935c801956d1ed77abb7a75ac88b3b478c56d71a899cff67801624ba07e4d3d9ec8e9ef6210826b42f6706ebe90b285b6c700fba0e75181aff0aa9242c26e6f6b771546f8fec487f1a9e989eda517d03f0bcc629534c30251114c748e277225eb744fea7c7ad8e66864fd87fe3cc9731ba4fdbeae4df5e3320aab61c678879f405f2c00ce16103a4021cefb9c6ba1d76aa83c4cb6d849416935500dbe6129ddb4dabfecd9487a8b8b3acd08dcedd39391ff7c248f6fa20482440d2a0dc6bb0165344b93137219cf902ffb92bf0dcf8793184b9451c314f0962eb08be9e33bd3cdb3c0ec16c04c2ddac303c7eb074c19b2fe196de483725946b0eb21feb5fa38c1250e432d317ef744e63d0358b9833926bb20e464bd61cb0d3bbb9d2bb305cc7b32a5bde88d8e300206e2fa75dce5c5bb60c5d6188a57dcc3d06ede7d1628d509a983fd3a2db5c630d4bd8acf9dc74e0becf82110e9afbc357d0ba495fc9be54e544cb9f06d185e103040a45e77d7e5ef9fe1b0ce575d21e87df6948e55134d1daa72427b361d8836ea5116137e763a0dabdd8f013fc3a0775750d5bbdf6bab45593ab48b52d48a8543dbf87d29269a4f7f391ca8b9d2cf567e06bf7d7011a35616bbbd4a267113ebd6e8ecaf7ffd36d911b4f35c3b3101bcb659694263d79c0abe99fe651b53c6b64664ace2886783ecb46e1c9827cd772b9dcfbe40c75df55943cfc74f91859d0b78d9eca93ec25e2f884ec7acc7eef009ff49e6f5fa008e9e233ac6fe35e782f2817d67498698649082854e57ab8067fbd58e96438c62626fdb8ef5aefadfde8b9fb26239623f3e9d02b0f6c42b8060a00ecb9bd2d0eeb27895466da1899f002e36b367df6cf60c097d6979239785bc05b24566aa775c0005f1734146369773ade8b219e49b74d7200d74d8ddeb0882a0f1524a72c6f9fd3b20f2d1cec32701eccc914afd8e03b905d79c2c5ae3344b543675f9375899b0cc88b0e3dd94fe248eee2f1487d9347adb0e537c5913683fcaeb72cdca084c531f148c05c1e1a2af4798112a776e5b8521ebf133ead71f8a69c1d38973aa895e9248c579879e2b8a6ba2f6bf9eb9594a918d9f86b2d67e4ca2fd4883d3b16648edb00cef7f159fddfea34bb8a7ffc61edd4bfa1620c6d21e5b41fe2308665584c26fe03d1442815842b9a2eca191d472fdb76e04c56c9036bf6dc9a3fab3ddc9b27eeb837fffff3e8d1156249d44e7ad8b2e932d2df3655968ae60b595150a451caab6cfdaaaf901377c2216212122fecf0d10fc8b5a64f397cd6b01869ff679f6284e43328870e31bd5c699ee6705e91f255f5559cf3bf17052c6b21265927786b56fb650af85496926f0ba791f4a650f62f188bdb0bc50a2e21d520d86cfbeacae76b20b7e683d1d6bc6142bedf108ed762c88f411015e81e0ccb406f7c5bf10132ca0f6ab4cae52ab25320775f681348facef8de826b811ab31a7cf544a83d75ce5edea6f0bd2125b3efcd96da14dc6fab72bc1f642c0d0e8ea2bcff9be3628b81c250ade00fdb226d1c1f2af8b9ef10905128d0c4bc4391cbc517cb27437677ca68f1de3e9d065da1577a55be6586bdc174b107067af8600c45310376c55cf8c42bd14340929beb55450911410a6ef6d44eb7633ac3151f2a34034d2f7e0e6b8a9c591c0311f29eb50b60a67d94d403a20dba0bd1df2897056c2425b79501af41a15a52abea9ba27ffbd9fac7e0d3ca3e1ef90cf8a927b2e1169a0f46fcb62363e306574f51d6a97490096607e01a41825bf5d7fab9841c7d5818250b5d83ecdb5ecfdaa9fa434fe4690e988150b65318854ff117744f320924e89f0f1da044d0aad00436ca1c4804e8881a1add1f60d406dd83073aa857df6102752c0eb5c34afb07cf9b5a76614a24cdbf9898000c49b3f31fc4ac34008f4f5967cd04679d7318262eaaaf3a2559930f358d5740958e781c28430ea36256731145ff82339c039d71acad7fcc9bc3084aa4ad80256e06c898a852ee36d9fdc5dcb7585262d729445e6d2d97dbe84c60de60beab36de9de48275ab65c610830a7c0048bdc5df4697a7a58593e9ae245ef40c92fbdfbdd1bb4c54f8c35a9a823b4cd7f0eff8e763b7cf416180f09be5844436379bf041ac6013520d31b24a00222f81e208d606590b8386d140b9c127a457c9b235c568882659780374672c377a33a5c1a7ba5b091ff6a86f730e00e3a0700baa2ad25c29fd9ab342bc28f78e9287db906e3ca1cc3c7f9d71534eb64d4e7d499d7fea2007d6733c31220104bda9bd500517bfb48a3da9dea5042c876bebd6110e7e07671d71395709e24b75a139441cc2d0b098398a7a5144ea16f2b435c0b601d1a2262318899118a211d1634cd6f7c0051cee538d1814b2751db3019538645026ab3ac37e555c9903e565205f08fc56c61a9d1b89313cc87bba931be0b200f10f27fe05d9708613667f2d67d717272f7ee7e909f29f17dcaa8f09befe2a8b21a8842905e2d5fb2ac111d23b9b4436e613a5b62e9e67606f4b4b4c8a32ed213c629aea7cea138d4c0c985ec251eed53b6d63212e517ab7d002b7c67772be82828765976834fdb60e421c7520def388c13065df21fde0388ac58670812e795c57cc7da082a501685323346996d6e47a04b613746881b40a56a497f6edc0eec527a16da13a74231addfca96bd765533c8b387da2ae08ff00e63c8a3e48787f75cdc4af1ee076b875e3a7f3337c04158bdd81e554c93c86cc7fbdb61171598122ea8f9dff93e05ef4fc3deadc84acb09160edd49ab8978bd6d383d2a21eb35a9e0207566cd9292c6d4a2d95c6b8835dc7c5c38b0d9df07da3591c99cea4b77031587a6f57daf20c56174e1947564dec8e692ca0962b9e4feb3a0106933467c71a31cd40c5532005c30a82c5f382894ed2f75f271977513d45a6c5c1abfdd0058289c3df1a7448c597da3561750e8c355bf2edea98f8d0c9dece30d661c8e43f2211842b0a7ce696bd8f49cbc4169c16994bdc1b2606b72cea8673797a3a7f22fd51f70db9fb6da2e0c655a606c6a357fabadf4fe8ea03d31d1279934b9abbacce51bb297b527eaac9420668a1fdac30c27b78d3ed36a198107ad4be8708ab144a14612f63f08fab65158c48e03ad856deda2990e5e1fb31b54b6e121b74d41245447b05be4b65efced816b3a8adabd99df9d5e3a3e4484ca44b3cd5123edd0ad09fb28b920c67a51f5fe3074925562f20baae34ac91d60b153f123c8694eab5f781571e9760540273b93dc72b43f05e05617e224530bc045730b31dec484cd879aac841216b3c6e12872bd28fc22303c2d73ec1c7ddbedfcadcbbbbbcd729545738f047032bdf70de13c3b886e0752e47bbeb9f85fd708d6b2ebe9d6ac946d73346fa259b57a0fab9252275af0e02fb2b85cf02f02b572002da1060d32425e10399f1f6e3d2a4ae22606fd0f895ba9afb9e5eec8159cdfdd1c49d61948a120d3850dd19f2468651ad282d16600c4c219a137cd3ccd69c5255f79d60571f261bf3c88ee0a642e99894b4acac4b3ab4bdd8b75062df3854183e2c4ba772251a15c32087b29b2db661b9c5e800d48140140977891ed38d6a3aa7c572fdb0ba3663c1cea55bb6caf8b9398c5cc6637232f4eeb8c7e49b8ef5a7d330770ec4045b769a16f831653339e437d6ae0139e77497c57a3794c652604ae815d79286f4800e72c0264f9eed09c111fa635263d10821efa2a04d4280756cec485ae04cd83d3c7eb69b1ac04b915f9b1824ced11dd781528ff52674b65e1d2c545c32c53ecf28b102bd6fbca6a19f574393cf3676ae475a72e297acf3919282bebec881d1f132c832ecd9cba01b4f4d3a1accfcdce21a29dd4ca2d578b798b6432371ec796da8ad2678f14252a9f46de29d90a3542c4000d02a985134516a3fabb41bd74f013eb5bb86a1772bf52f7d003969e351ebc942af66bbf907f8917594b1975dbbdad759940504418a9b25f75a32ae8f44d3369689b297574ae4e81a761aeabff6c58c6b4a6ebfba1603376a08a85dc362392bf3a2d501886e95316c363cfef2cbcd2c612a0e7b0d06229bf68eb71d0855312298bcc706fb789ee932c4167ca7552ba0117a73ff4384054bebb3125560ab76592f5e376483f929b98854c364d8478df167dac07412f3640b2fddb5027f8096803c7f19c534e0a374d6560ca55559ca7670e66e2df8282b6a8f0c1949478c7813e043f4f55e3b6179552e24378f1242fafe128903243eef9d7b0ff8fb55a8253bef09a6563a4df459e8e6ef89c7b13686b4f23786622f6b20ccaa7bb6abf75785e94d541fbe329ced4b57fcef9eae86028779602eb266f36b008422e56f2a18ff584667764c549cfcd0e27e9f9ce2d0f4a1b1dedac28e68b75ed53d097ed3b2d64cd719a533a5ef22d8ef9a06606f94f20449d46685d92af6811d5ba0a6e7e2ae1e21ac152759b31f8121e07dbce571d757afda22a9d9e81228797ca36324a5c302b111d96c186104a787111d9fb43a592a067fb43bad79804931f0573cb90d3026e88260c5ab000225ad07b005ba11279166e717c091693546fcac1f674b1b8dde60c11d5be179f64649e53f5910b21a495325e7a2d7379ed3a0920dbdc968b774c8a0d2b6a56634790ae5c2c3e54099389f0eb10b1ca4c4b40d0ccd359bd856d2f61413212db57e72bfa7e24454e7524ebbecdc61dae2fce2d3603d9e65f75a37d298fbc3f9a012c141f28e0adeb267ef5a692181a8bbcb404898681e07606b941f5dc0f6167c30b9f3291282524dbbe85ac2ba8ff5411fffb9ba8c1fb2168f98c2ff82401c79520904c7955a4869e8641e35cd0966a1839bff2ad650f395d20de7b0444e38bce67780278a832216258a5f646a2f2958faddcf94f2bce5e326f8aebc382e283fc4c7ccfe677f2a23423be3d34680da58b6bc6b083359fc87d48c0082e3872e57f80f09d3c96bdc9ec781fcf5d05381c9c1e96e1a1cc324b14b5fe66ba4af932a8d0e5efdf859039cf9cabeb9b916418f06ef2dc53fdad14faeb0aa1b568f616763481746e0aaf2717d5f848b4ce429a85a39f8b6bb343d475ab8774e54537a74db2a6910815436487de22b13d9959ab8bf2f97e443d71ff777bfab755aeff74280e1c6451142c7da013d86137d4befa76a09bd216d5127ca91b62cc1c69f324a36dedd8f23465486f10c12e3d11dd3426bb71208335d3bbb5c72a558ca3f40977db0c46cab5ca63bab848d8b3c4895df7678f5c4614197265d2fffad21749d1b110ceb0e8ebf97ad76af01d1eef697a90a38ed39e5d6106ac3dc200295fd5fd175be67fa64d2b5e23170f60d8ec98a28c67a9ced4c67a82b25315ca875993705edd4ef397da1eea1e6f931132a93bb42d644326578fbcb3d2d60430925382b74004919f262852eef9c28d1c5c953362bccb31e8911db1b02a3e4fa078aac3d833dd9c5b8ac79b4f68ff10ea18a0e51c8c8ac407436e9f86210ebaecef75fbdfa548fe370069c63ab779f07902a48c1515769f80f9c249dd43815fbe1174948557bd6807f894af3c347924f6a3e053b77b934d8e8cd0bc5a662f4bdc5d84ae10a1f19a7084532b8061561f4a1457ebeb0f6775089b522709afe4879af7b5deb4f7f45f61d06ee7531a46c9704f045b0f1552b510d83314876ad49febebc1316a9cd445a90296fe13b4bc8dce663dc6057e2adf889ec3dc608096e56588da61c8ea3f47d95ffe065ba0fdc795d7b5cbecdec435b80f70255d5b8b177580e768cf23c613bc05d2183713952357c013396f10651f4af9dae667ab47240e52b041590f9e358d24fc47909159925460022950683eed961300387fbf0fb12019fe3236d6fbf67afe56b338032a986ae3da30530731fa1fbd273f6c8044d533ce6b37cb39bcdfc59cd88c8c20ac22a9ce3a302354763ff09db56562c7db93634081dd6231a5712d97eba17705dcaae7918c7ba98320d38b33784d26e40dfd5fafbadf49da991367c24f788f4b8b071ae3f8ba30f5f13cc95d073ed0fc2c7aa6fba8d72d67099be288e7eea3c4a2a0361bb332222faf29658b5002a95b9350e6dab87613ada2b9986477d9e43814e5bf492b5cd18ca62fb092e5055ca61382536045ba056afe880e536114fa8ca21848b888346d321c61c4565577bca4c54ec87909fd72a0417340b47fd0154a255a2fcfa144a5798e26d73654773d0ef9847021a3fa24bdcdfc7301be5aa955dea62f44aac4ed0d2cdbadba11a6835a0112ef286685dbaa75f2d7230b6c782f1d8265f1eb38fefd4b88ccd348958bc18c7e699fe7ac5ba19f09ca4887e9d9776787634aebd8ce357b3459e9e2864ef5e686c9d7765c0db9afc5a02ea049c2da6b1fd38acbd8a31787f4ad7b99027d1b135f22d6c9688c60431d7d94a25e546fc2326a309429d9cc195cb74b116b6fc23f0bdbb2a63108bc985310c049a78f77b4db9979ee8d63bd68708ef3d2d8498b19bb1ce3f0be7d433a108d01b4b7975a3bf9cc6715728a67be86e78e01446a0376dd8bab63e7724b97d1d948e6f001e3ec6bd3c63a549023a7fff2f785d10e1847b3f4cbeafdf921e87383eb649a2be096fffcfe09044e8f5ec66013ceebbfc6da43e8eaf214d9b44210738dfb30aee5e2385fe54d748f60d671992c35c3ec74deddb4a56baada77df73fb9f99799bab0add56ac013063065c5b82b2f9c6ca9bfdf89df6e3aedf651c4359ca08f831260d1ea913f6d771da635fb17c11c76440074fcc35ba0341242a8c9b3e2b4343cfc1364acf88258b98389909c18fb29715c4b813241e33cc88d0263e0292a139894b8681d92009e61966873a3163cc50e693b6b2005004aaccb9a629fc31b706d2e547ab4c71062700ac468b8f747e6c284c7e6ca4e51e8cc882f7d164ce243c58cd19a54d51de308efa490263ce0df77b240e5846f48d6d796c4138e441f6c86d399e7af7ca392d0f8183568925dcd4a73599ec18811d31dbed3b5716439c84b781b40ebfd0e2854cebc7e5cf301ebb06edea7649b173688431e497e7682d949a543af004dd010345dbc51b94809acf71b978df37361b43cf9920e188aaddb4beb83c77c26b0a6da0f2a7938b9bac8d94c3edcf63421edb350b211f5028ba65bb96a288ab827a434be9df6ff9b5b8e70351461054a861a39cb17828dbc2528ec2696a9716c29b119bda48eb829ae70828a4e9079f64c6018161e93bbc2b358fa72d3823e383a70042cdcf7b1e9363742944af71d4d809bb3a1d56a7551c684b1b9de1ca08c674abbe307c7574fd28d6aafedc5ed0bebb3a9ce7691f4552accde5980aa533d64f435076e0f34d0bb199d6aeedc2873eb080edf9850b73e11ba20ea5b7457e6ba88ea5f80d71c00f05515b1e0bb35ebcb7d6be9959ced92066345960277ec24ec87e214e9457932052093a0ad450f9e5bb377d5b6e6b278d8904272fcc6b4bf0ce3178a9f2688170809793932e69d64b1c79a75678e1db9349b1e2ebd1ee9740ee97171f87c8d965bd54fad6ed3f2de658ca82b6ad53e0db9ed33616fb4610a7ed49de7fc680d4fcf4ca2b00e2b559bb1f46e237a0cf690a41202f41491b23e3c26017838cb3cd31450c6cf27915c0a2c1abe7c1289407ff91aa46c88a1ae06725c26172efdce337afff07b606b33e2ae6f9edc895bf634e88a33a34f609f6970876850b017feee0937b342effae143f6e24b3fc7a405ec29d976731d6a87b0fffb413dee9fc79b480e1db7ee7c249f984ce084435b30c52d0a4064b71a888a7fdea74e347ec206718ac8a48f2c407bf5ddb8237fedd7c9e8e9ee2fde94b60bcbe06df48186620dbb8edaf5f645c1b2163aeb03784b6faaee54e57987a534ab880d03e00e63f9703cf3495c21fd661455034a6abf7da5f4a1160a994628773e5608e6aedfec26154a50d6b0ad8b5e6003fc5436186e7fb9034dc1b9ccc7245dee021cfbed84afbe66a2f0ba9eef4de9519779bd79c5995847deafd8e04ea9c35375f49c82ee5514c257e2090ffa3da2775b81ef1ede08b67487f16ac391a0ef2797fa53272743b4c50638b2b1fd776d367dc6f1875b2e7ae92bd295c93ededccb7eb5ac3288e98abd33361ba56961258a9b8a0c2cdcde41ef8b2b48cdcbdf5f9f359096aec4ddcf8ad91b84ef47eb6b02d335a188826505db2cc992115a5b1275e345b025933c3f6f9d4687697546151851af06dd66f6425e123dc6aebb48dde37414b81776c2b16848f58ac5029bad74ef516663c0e4d29e8c6bc33bdd82cb43bf9357e99aa0a43dff43735466c126a2d39caba3d9d9e71c955a457f0371b6ba406a3145635ffcd8cbe85b4c7019e5a20fb0e22797b7a638eca2fc74b146aa5d511c2e2236ad89d6857f0b639274cf54513b78e66cb25abef8180b12df80678943e84f35660ae0a31fef0ea0aa0ce913000544ca4915e097225d6125aff83cbc254bd6123ce9a8f2793dd8839a4de4150223f4e72013ee72426083e10d57485a34ab47e538e95832dff975bf39b0e4e469d1c79b35cb69f10165f2631566782387ec8c5ee17f8c0a66df40c6107e5a4db29b8a97a0e03a2262e563dff90b6982a626949da6a52b095004282ac27ef2aecd09ea7b220779e4c330db8666f30c36a295dc5ee4479ad2a2fab865ff8ae054c45e32789fc6364200849ba491681ef16892769f79ded43617fc2071c4ad3849f219eca9aca4d94fa3020a8b87ffc8a83d0b2daec67e16c32c2e92938b210569f88e96cb1aa98edde0f536349ca665ffecd8726b97b29edba345b3bda076c1619507f7238ea80690858f2d75e2a15d0c76cdf2e73faff73ce07aeb227e4ae94e19956506a08e37953428aa76f6c427719ffdb41580d3ae5179ba548a5ef1c5f60c33549d08da1f53b465f9c6d84f61ba6cb433a4391b12d2169a907a13bc1ee98ca0d41f1421087d7ccd6f085366d99f187cc2ec5e4a4e88003dcd26ca53a0d7f8c2754d49fbf64ddd0f6d8c56f91e6bc8ad0d95095474936e6d15596d05dc9db0c040ed6dd11f831f95f7a744fd90a0f2a24a57f81784d6fd9978a303795940f99ed94c31ed52756817f5b20a36143a527a74f87032b6cb69d332495473d89ecef13d58af72e8a52481dfd5dfd84d05eb16f8bd976ed89c3bf496fdfb1fbe63218748cbd9978077ed6da189c4c990bb22adf244f7085260a1a1e111d2bd212a331002a1f954e9850471f4d499cdeb75c291e1f0116e0ef50686b3b5287723d83d4f8a824674db7c4ea0d7073ac727ca1570ea561d75a1da99372fed1e4d49be8300feb3ca25c1c4c4761cca76ee92f8cbc5bc113334979e178fa3c34c6e444a130723642820c9159b66bad9643618080bb17c887e646c3f47aefc26c216bbac87c1d7e29dad54b8374d636e86de13b925aecfb1f77001741d1ebee20c614c608cebb6b2047ca695ddb35f11c5abdab1f1cf8bb1bfd0e2e36e503e1bafb4e072010ddf5ad7812e5a256429a716accf8108f2e2d235017c6547e2cdd760c69aa6553f37a047127baa24a9438443662a8aa8f4f79b32f5251c6f77a84d6cfff458a6bfbe589f966cd39fa7ed3dda8c6dcf0a821865545adaa9ce49a64eff5c63f8c50e44998b489a6b7bd1a8ad9c7a40a5ff8a6a2d482b9d0467fa21afa0a3e0a3a14a994420518082d78970f02c56029abe95f757b3eff4d031724a284c3d622ffb8d87933811af0ec390b09505854044c366811b77af06c4b8414ffecbb6e473220287a378e81d68c7dcbddd05d64aa802ab5a86fddc24bb3cb1d3d000cc664fc814c5f40a0c41a578169d51f80b6754db7af8b7787d85a15616305ea3b3196820e5e46b28118e309dd933c89f6caffdd7f38954d5a7d19924cf12734854a33f54678b3996d12b371c41ce9b8eaf2c7dfe767c3ca4d387e0286c3e13490424360af151fcf8c73cc8f8b6e28cb87316b6618175f5d1aac45e2bdaf9b4092404e03369d20c8b8c7fd88d7c8f987d749eb5660ae7e5d2e4512dc9ab9cbfd492308ee67e6f71828a5aadd35db4e9b5faac4293316e53494a7e94471cec29f1142374e62ad51da910695c48523bbdf7daeb2e95dbf8144845e911e2a211543e18bbf987416b21c8388552f0c591b2d1a700ad838f5c52fb1018bd154251052228bb7b44f58ff8f144cb7fe4d1afb72faf490f8b373dc7f7ebc842f57778a4da4a5d1afb37390a3a243c981a1aedd5deaa74b89d9a64e447505c471a1578e372a9a9eccf5a675806b7c0dc45015d30fefe0c6e6f4239ac03a6bf6945ad5256a485139295096844bef590637137a9155c7d352b8d4b053d258ade647364bc9629aa5cd264419606ab3d2c4626b371e4cf99a9fa223ea495a29b8227dc21ee83f547a1b4ddf6cd3aee2a9b29b55e838c2968d8e6c4f6155fe977337fad41b577648c917280b9ad72756977e698d6691aa3c61ab6e33d9eecd4a1062848f5d8f2a2fc9cbcf3722de32ce88226518ffbf53475d3df2aebaeb569bd4e957c85d501fb218089dc063ef1eb324c83e8702c089cb8fa33f8f371af912028304e81284a87654de6d3b2a0a0358f35771c1249bcc04082496b608e44ff05abfd13a938ad6d23045cb7b41546cd378b015990e5829f67ec5b735252c19991db5cf1bc31f95b91f394c432e1d2ef80c06ab63be69800eb81bd27ddc5c9656cf577d177c84ad3a9403246ed676cbed186e340b7759eae62e4edc09a544cfdc274a51b20b44c58d856aff65909486dc04e9dc16b0e7a2da910a75a9cf9ce07887d91ab1b10ec10ca33a70f964b164c46e40f029b753aee7e4279d5b0c0a2f8222d3616e41031cb65e69c32470cdf3c00987529b94f2be7237a6c688b0fe011a40ef3c6aa2c7da4ff8ae8b52e8a994df92b6db8bc3364aa8912ebccffa989a38ecc5df394b96cc103f69b3945a6f2d16d355e8d0cb9f28695d265ce93623df8d76e35c462ce73c54d1c6c46853de47ee8371351da4b65aa8d3fd6757c1f5bb1b635a757c2058f1a7bee69de89bccc15d8d7ef6d4a7928bf7e203ec2274b27226f003db5172c5036093303cd8e3c6d011b9b25055be98b23ecc43fb10507e86a490299c84922d1d3d5c1db12eb040bfe85e774508c083239aee58b260b50fc33132e2cd6593aab790f66ca3b2534f4b60ba0aad55dd6eb1d45cb2e1e38384cfb3a1f5be038ad3ad7927d02fb4d169e2d1ddff6bca2e067820d6bb7ce58786d1d9d9c66cd34d7103a1e6d00624dee8e3ddfe2061d49253a7407a905d305e63600a1e7daa52cb283c8476f2f0848c4865d0e64b1423425a05e688690a3d30cdb6aece9a060bbd8710a80e6bd32a02ed39632c2e33fd0c4b0eed92d7a77b36fc8d905c2aba854fee3581460ac31643377fd236fad7c47a27a43194a47a42a71083cdd610aeee421db1567c79eea1cb9765df2733b5d2d286daec1cd3e86449b88ebb004cee07a051a61a4f74eec349ddfbc4c1bbbb6efbfba5977e5cca3c746a54ccaa4d5f2625167dcb098aab5f1f751e7256b989a9933a3c32ddb2f7a618036bcffd771bdcdd0a11661907f75751109b5ead5f4fd8e5d27cf69eefe0e614fdb25bcce43efac0385a2b621c246b64d601cca72a75c3550704d83fdf0f8110c68d012869caacbc1eee0150bd9dee3042d2da4443ff7ac1190a3945968771e575d491985f5492f2e2562c66e901aaab73bbaf0f0818904737ed08f5637203b27fa2089d3bcdb2ae592e59e387299b18d5821c2791c6ddaab45ebe61e403b128b82bb0e5f5e596e1031fa086ee2cd179168dab3dac53ba949d95b499f8cee74b87420f242f1741b36df3ebbce86e3834cd25a1e60f507ea16dd851823f29c86d9b966cc41be1f4feb521c33f35bbdedc81413b58b22309b46bc4724c5bd90cb911d75be481a85f9def394cf263d5d4663e589d4b4afe122bfca49d85cc4c91630acdff62b9ad6d80f720420888fe76d9a423b832faca04789eb88f4c134c7cf76345b34a8621e20499c3f33772ab32ce1bba4bb7d56be1202563fadaedcfab0a6090cf0917c1554bb3c2f011fcc5f3a22cbdfdaa3b7de2843e6fd933f641eb2dab294b855589ecbbf27f36e8fa408c14ce4d25cfea25ede3b38a1d14a0d3af0b5f4dbb58d9c730c6ec05b913db51c5b9abb4fa010dc0a5da9d10ad7269c91521d4ebf6c8979a62615c7e7ea34ecf50665c57f7fcd9fd4b584a1a562f1c5dfc6415961fe1f6e0db8c1c91fa4bcdc7eed982ef18294b593ce6e2d1d939bb1ff49b71048b47a7ebd120d9f540e1b1c5671cbd6631171b43d8ca6b5f46929c875c10bb98a45586a93f5be97ca7f2505be1c0c0345c6b397d9439a57a161bd3768a382f946248793e9b449b12f3e2ba18ad78e1b24b5495928830ef81ab6411064143fdc18b09b471e1bcb48b2ca4a99cc17a66ad283fbbd73343c0d9d0aaae662c46e784ba6e3fc57472d1e8528fb30cbe4a38176643b5ef54275933bc11eb4a8c4bb9dfa8a1230d0e91854bba5df62057df5dcd72394da61e10678908f9051d0e6bedd07123a9c7b8944f846b379c21443210fa4474971d4f62fa5fceafcef82d2dc6321dc9ea008c2a890f1a04cc9b4c15aeffcace8907fe3b98a99e16b0090026aac0affcadc6d0599f9ba76741734c23bb07b15510fed6441761defc1315ff6dd02fe023d2d02739ad02928e6431898a84935e9b96f1c9c39f8e963ffa86afa2123fd3bcf461012d8477eef51a2f0f45a20e0eab83048215008fc34591f807549892c8275b42913bf3ffa517a0c73f1f396f0e87f7215fedca3fd7d3015ccdf750a347875b7891876f2752c205efaa413aabdcd720203915ba6592c039f34ea554ee5d6f59e40a22b05ac8bb743154a87b3860a74b30dc0ff08632851017aabb23734a0f22ae7f1f26df6a5424158ba96ddc41945436eb8c075789c838b7b9d870a88c90f0c550d3eb6f83762a6a02e1a5896dfb93d866f9d030e0ca98e83035e9ef6c477b0c657454c0e961173a59bef74d2ed2404bfcf001eeea5e74992b4439cf517d056034d99171808f596fc2b28c9baf13d4f0efc3aaffae04fd8dab0e757717fab6f1fc17a5ff7179c2ca0abd0ef7ad511d109d749957f4922ca0cb439de00b820c224b67efcd486112e14fb5de58035d9012490fadc6c6e02cb357c646438b275100fc09978f7971333807c63a0e37129a2d890825d300d86c5eb6598d3fd19993c664c2a0881acd8b4c57bbac95b261fefd4f9a1ceb3959a2915ae0eea7065f07116da880840625de15d9d72e31cdce3ef6f30c32e8f6581c1a661ae0fea4e8dacc6b0938d1674ab62ed87fc81630de5e231a918c5da790c0792df36a7d63eec77c13f10f878cf696af1a774f699f63652df587067a99274f40c6ea8f3517eacf1047001d09ea68ffae4ad3eda406ec2da8f4f5c2820d43bedc8a21c90af6d1a5a10b951c5e2314ce748f72024a40b8cf76dd359286046aed24abc65355d0ebd1a05b2a596382577a6e307872ac48b62516a3615d9e5e0be52cf8c7cb365c7a2d8f54133484a692a97213de314b592158bd11ce16b4d949b7013c36a2c2b7ea2a247e1505d4ff7cf65d6dbf83a94901b25b2ddfb6ddf8680bfb78daf88d2931282ac5c197095ee14fcbbf69427b000fbe659863ba1c435d47d8393705b51f0d09f7944b971b7e34ab063b9c05b876762b9e6aea71873848a06210d496179f3d677eafade5d130ac0d8faefb2f3b11eaadff2c2adad989fc4cc34b10493726ca51fde1d0a4a7a1e16b222d5afad35e759a6f6ccf04b071448e01e55b4487d634332032d5a661e12f249a2d129e49b5f5786747b4be515f15d7bc2770f50f5160a4034c10e8b38fd9762912b2bc921fb37c5dc53266383dc2a26c8c76cebd5fec9c5eb2ce4f5170d03b981d5534948edbc29f895c58e498fbffe5debd7f81a9f303782a28286fc706dd85c61e031d042f4e1926df37087e450b00511a8443e56c58873f9b46218d9dca4555c2300549f0a69e28089ec8263bc9d300f84b292b6f801f937af3c9bda463eb160b1c60674d0180f118aaaae27d452382f41305e37de05f2080bc02fc5513c5a545b5a45409b96338a6bc5339a218a3df210ab996baf6c2cb10c7ec5843c2acb23db39d2a9712c2c066e7c9adf524bc66eebbe4e5220297c03af0081328e7eaf64bcdf2e04147fc5c673d71a86edd283a21d3240eea78e61f3c68396e2e4a72b6faf770e74114db0da262654ed4b7910037a4d525903b582936d6f4663575fb12b567c59879129477e5e9b022af31aacd306771758dfb3ef5d5993c21e7b7643bfc5391d1e311f20e8c07c81b1733882b06784a9083bc8f1e8bc600e429bb6069be5b1139c1d8c682fe172bbebbbf3c67e2a3cd3b80860e9098d6df727299e8205ec4aa751479d179e3cc1215f756e1a2cc75c06b70c7aaa7eb8aed80f380dfcbb2eb8d399f51962f3b5c9da88ac6883a3a441ce24f18430fc7090c497e952d0a61452f338b8c5d1e1e8008790129c705b04f6d64077ffa3d7e58b525e0a7bfb421fa2b8b8198a046897ea76f5f13e47e570c9af252ee4d619500a0226447ccfd800b340a15451b87ac6d233a43cd6ec53fe31d2d31049cd41d06d0ca3cf95c1b8b22c3ff9f2f32fa37ff94278b4866692627aa0379a2296daba2ee6687d3eefbeb435e0aced50b37f3c9706a1ceffdab6407fb9424dbe8ac20254e34b55d0ccac2bea5fabc0d28301f73b2c4eb3c989f4d680688cc4e7f6dcd470636ff22c0b186610e49d84670522bfd65861bc0cdaf3641d78cbd4b67b30f3cd8a5f96160816beabead3e88af02eb2f13d136f3daf8507b2c42a90b8abbca2100cbc8206e6c9f527ceee12f721ea8bb095bcaf67c629cd4fcfb21f4c9d0c8ef88b96982f7177ceb0b53a365677d2e562eed6c775cbc3696f2e07484b9905b55b28c5cb47d22353988d392548618d2ca16919847e74bcda34a8b9d58d4ffec5c50d6ee990035f86e7d18f9858a47bd564f9cb147856e13d059c01f14c10842b51e035385980d0830c7a19d2460d4e2a204e39ba8d13724f3602bf27e4e439edfae56556bf88c5b09be0b44ab4d21bf7e1ddb4caca53f4477303ebad9212bdfc69364b3e34362f111f01b846c88fcf599ddade50c0703b426ee9fccad5e8f6724744e5341c14149f80339a0746238702e9e44959d46466cfc38e2dcf717cd9f89d6956fda60fa66cf727ec8c045e10eec206b71249aa3e979b8fd33da53ef206c45d1c6e5ff5e6edcd08f8c1ea21dca9d8404e7b5e75a4493e7bf129f2d3aa18e70216e295d0386b8dca5fcf6c1af105823bf75e3942358b77c199a028717f3712707a975ac74afe35a33993f9daf0a38ec7fc156ea493f1f1671ace7aa47eb32f3cbeeb9027d85b3707900234d67d591b99c48e00eb1cf43403debef3111615871ebe78559d4487b9ae51749be1133830eea07d5ab5d5f709815a487268b59cb132bc07daf03683ca84c8fbe5205514f1110184c5a167a9b745337ff4fb5cd3ff117e26df2990c1781c4590174c2cc43d634558bd500c47631c3cbf046e5a349c5e6fa3a7636a1804df9d7dc174c63e20cb26cb55f655321fa6a9b982111180aec8b08ca4958060c06bb57f3bb2b0ad8e88cd3336b10fb7fb87fb8bc5bcb0877fa3b0ba197e2db237aebfc861bdfde48ee8346a5cbd8abbf80781123f9f09495f4771c756f8659ce3a1bb92dd201802519da57ea44951d7bea92b2da7b06bc266588bbe1e6c3e7763ba8e918b3fbb00b063f697c0664296e43ff27b83d31e402ed8652f2f9d5f80ad2bbb9b9c0fa0e4c990dfae5866969cc34dbc342a33cfa02c12b4a6502c4a88b7a39410e2bd84ee70e0cd29d45ba0e7246f411979ca062fbdd0c43934c475a717d9d00f7815995225337df5b25ebf655ed665b8de764aa8b262f010495a719d2f00f8fb1292a5be6af228307c116302484248d011d4519c4be86703c4e04acca2ba05b2454698ef47a14472fc156769a8b78352bb81a0c5ef120155def65e6b7275f163466c62d891a9565265cc5c5d485098f5d62f6ed206745d8e9fbabc3db0ccb24f4937a541b8b6f51abc9fd63abdecae1a28364c759454e7349cc00ddf2fa5ed72c43efabcb85253a812cdb435d120581b8312226889da4018e1595e075768939879e634dda383bb6e68a5a2e406a85ed6c38b6258aabac7642025b5654090548a07142d6cd711f1124e16401eb1062ea7e000136512333f0bc91da773795c42528f26ce6e4873769a942d2ec32f7fd6e1c7cc4344d214b459266b1c3644cd3a476030200538c3b95b9022357c51ffc21c015b02169512c5fc9416ab5c48036db2c823d41ec23fe23ab1a9363fe8464fa25f0fc9221e27ec93ccbe8176d41699294e1d9a7d77b2988bce0b762c19bbddd3dd19def1c0c93aaaf2661f1b56a80f439c0873210f5b456c89c97d0697a45f6a360720c03c5b19c743f787b44b159c464c775e6da33412d6c34a621637bc9dcef1a47aa93eeef5a409fe809b5afde2f612c2cf416277c6137eb2d0d6a012dd5936d7381f804aa4ac2fb9e46564704050eadb83c2845860e49f6cfbfde9338766620fd1ccff38c1739599dcb3aec569dc04a80023a176020a672cdb28d2f88d4b75496ecbf9559c0460f7eef5c567d81aad1a2e04f94b08e31a539ddf5aa94e2d4d1d7bd29492bbb36cc156afea673f3a32899f271a1dbf892e9befe93374abec371c2dec054c086c4354d072656775239d7adb7fbcce55a15cc22ea79bf7c8e86091be3edaccabc4c34877745b65170f579d9de55e061e900b3b91231ea657aa8ba16d4897549d4f3f7171b5c17530ded527ad79a1daf2675184831d3181e03b2632fc9a5786b0678367f3dc60c05f93f6aaf2647f92aa165e6be2d905f0e7b240ce0557a6805243da42cfc8e52c749ba46e4819bb4563f2dbee42dbb14cbfd084bed61e6e26d49b84ca420e0ff8ef34a78ba7e52486c4ae23c808912da9037867533a20102e5f6e776637438dc8c1bab501390222e202963cb5ec97164ddee7660220a057cee180c80c260de3d5822158d33b65b39132a510561595f3e9721036b97bb36c95f5149765d93dbb16c7822feab08a901639b725d9aff1db27c1db382dba6180921e4f1a606042f64ef4d4d0230fcddf3d07ca64aed5db09038af2a8fd2c00053f11e9e413cbd0cb69dd4c0dac4fceab3ca4fb5a6afdb679c2d63ca1784fa18b44ffbbe428e583d204432d33d6210ab32f548a67a9d8703aeba2e37025745bb4bc2398aeb16140c767008ecba12dce5f56079504f2d97354cca56707b44034ebeae9ff46a178b8d5fd1303efb45e59a0d5760a4d20bd5fd54eeb9d187d5b581bae261dca6e5da8b3592fe1797ea9318534819cef350e484829a775d72c81f4451cb2813307d8cea2681a47adde0e0949a786075db67244f315e56675bd9ef967d82ba0217902d0cced3aa1861940def77aa4e50291d39daa527e6349fc6ee283e68f194e7966f908ba593612f6d32fb35bb02c33187474cff8ab71b0a6516849adcd012680191a76f8f380f113c838750798d8f299e544668e76d9f1be089d1df9da4c762f31d9ffdee591d2c6c1cbc61338247448e95a7b19236d8814df5ef736af311096853fbdf3c75c2b119099b6bed5bb6e674add0e8b3593b729f72f97b24ab076447166ab24f6732417673e4c2aca636a3b9b5bff9174e4ae72270babe15773c154b3feff67074ebc4064b571feb5d0c995c560c4677073e983b34253c0f7451bc667914c9dd69ec82c39cd2c3de09a8dc59c532ae9a4d9cbb1d8b9f6da135022f7d084ef2a1c65b4a6e23feeb09de9eb8a609ae2ea18b31e4968d6de8074e25dc43142024260dc08c7d12a853ec280a07d5dcbca82f91d7899be389939043b760355b8ac7e6664b82e498977ef1c44bb6cacef70b9f2759cb8ab6d44d54b4db04d3ff5e8d06bf031987e70f6401ad3f59c622214c9d95bdaab0fe49153fc76f211014b3e8c398a8c37bab1167f364cdbd284224473e927e48896073dac2259ceaed065caa408552829ba1469c6557df35107abbc02fcdb93fa435913bd26f1fec89d68afeda910d93bf675a0ed96590359ebda693812d1f624e4b39a05b122a2ff4611e1d87e5678eacaa78bf77ce3ddf87efcf8aeb6c633291eb73c64d2ddc18153222376ead755c5471a8b066ace4d182a8dbfbf39aa2a34bdbb0f94cd30c2f4225c7ccab4df4233da5ee4d8b5f72421e37ad39b38e2a8669a69b9fa8f1d0d885578d8372998f1e04103441a4a4da2f802d5326dd299f157b1a24bbc9c3bc991df0db9257fb1d0ea9599890212f11e01d0f950a4bdf371d21c3def495d9a745bc374578c407e18acddea1dc872f073a438f1ee2795d99f84ec40596a295ee64f1725f0982d26de098db6fcb97395a2552ac87ee45f0d8726b432fc4ee8fbe5b0033126e3203974dbaf15cf628f5390806d1c0171b4729860feffc2f0905c39d62e44e374e623fad586a301b0c09db76fdd53bfee6e5050a37ee1d57c1afdb12bdce8867d811ca1efc004f6453f2ea059fe662d56e9744ec4a9df0fad2d280a25451be9fd3d4ea6a3d92c846548d83748e7ac63c6d4849a766b1cda9d6ecafc66ed5ad292eafa4d772b16e18e841e6af6ab4040745b2a3305c8d69e77dac682e88f549f4c8dc6c293b44ec62cc11c8a16029377bea03978268322b972a6f2b8adfc9e9df7adb98c350ad887feacdcdf633c9fbe30a42de38c0d87872415d32055e6cfd6bb5d2e16291537a009fccef195affde9df69fc13331920288b6372026230ead3691633a8583041acde585dbf39c6d02d074ee1e19984c707ae6441283086c8f83e83a6f800c4ccb86e8bd1106004bdad63aefca7201a332427c0fb35357e0467884e0004cf669cda3c3abc74c9635c15743765757941df488bf6ac6676eb04368e20e90459d4447a40a3d0ff2bde3082e0a8f7d210a339900c19924462397ba72d9e24d2ac8b694bf42d1ae1abb1c9b193e04532166fb9c7385b55c3766cf860c4f05fb726945ed5f8de7061d6a912db27621ffa884c01563ce39d97933fce996250331955129c13bbd146d9ce2257ebe0aabc6f5117d3d09dc8bb40bd1775303d8806418c8a244bebe5744cfdd2d499460e14522a8456d27f8b5b9526e5b9171897719b1955c009c616caf6ef070ee62fd2df08ac1252d5457ef680877d8427c5500769b90f8dbe7ffeabc1373e5b510f8214f14d7b28750140b0b0cd78dd7847bcf4c81dc3f7a06441d87e148f73f760798dae3c95f84370ed26720fe54d496562a89796d29715deb855f2d2c758eaeb550eaf4ec6c234726e4eae221ae7778663dc3fe0954dd32823dca6557508d45e19fe7da782da325fb78425b439fc32f11fea9a522f2de47695adfd39ec0329d24484339d0084e4da114f39e3b27a0b188d62385a7e6663970d0a9cd2053e7e18159e10dd680adf634ba12835fca7aca9873f4f30dea50e3b3a46ecdf03c92cd238f87452d644ac40ec550a2b73047213fd6c66dce52094a71a843c4bb7ab0cb3ae5c53ba9e34b2edbd5cb72e301fac39a2853772ba3326ecbc48e04f6fc7416e19ce88e5097f4442828da186e418c7d04410caf20c59d3b1f5505de0878beaf2f3a506361741776592d9866542a684f5939075549b23888ae415337c5b4ced5ecb21f4b507511dc0e83af689f38a3b0de9164393ec30db94cf112376616f626eb0b150b49dc4ef601c0d182e9013e340a7a48afeeb72f8d45f3605d6dc6496d17b67e10157ca9e0ebc2e071c98d71ab931e9c244a90581d05f2cd4ad4d2809799834ce8c3229c18b4a3e0435397d34cd228fb633be9b609789bbe92103281bffeca3ad50af4edd153cc2a436fd5a7b0ccdcbb2e470cc00aac1b74fac52bfd1f7e378c2ffe85f798f8f39d9b0b4ddea8559336fba06e9e0a3cabee4ce0c494e4e023273472e94d6e8d69ee79bde9d8785b962e88dc023ad9c37e8b0ce43bf11fda20f53a654e9092b1a3b2e5b5fef0f58396a1af7abf06902e0c40fe1626d507ab193e81f6db7c53ef263e036fc6b039d11efe900faee8edac601c12cdf99a0c309c7e947679914dba4ca032ca33886269a9f1278d293d753844ead62ddaf28ad44330dd8ae44598f6cd678b1753a64dfb49103e20eee13ac4090177cf4b7c2eed8958fb2828de793b8b04e14412a2559238c341de0377c3b9a1cd6041d6a010a8e4e24ad455716fbcc14d7a5b06af94db6ffdda6d2e73e3f1a8ed7a32953e6c18cefce4e43c39a6e834be13dfdb89a14fae97d17e295e5a80d5d777d89112921cdc6ca34a05fd039974dfe68c2395f4b095eba2dbbfb1f334896e8af1d9dddb82ff6ede25b4ad1af4c5f5315dd9ec7293b4f55d24ca6564ffa7d943a9522a7ef1c9f059e3412afb208c49069136ffedc8f3b9155350da32ca45d2a11d84f4a49f150ad916f0b21d6c9954248306163af943f0b799eed8b56a4b6294a1118e02c84bb7db72768feb0c214fa5d5cf7f5a6597f05273fea911c95cbab59336a3a6da8f0ab90ba951dba820d08c05fcd4a2a6fd345c6e1f25cd635ac248f4632dc08e2e322e768c28a97b3a321f55f90c9e64534930227050d89dca488e845cc0ae390cf757d4f90c904be1a3f3daef957b4361565bb78a91ac36d41f8571f1d03de55322a3314198c0a7e3eba41d706765d9abb39be2b26e3bb7e20a47b03c133bbc11835c8d83c5c18c278d5bf658fe14b907ecd4a0931eabba6a37d5597b9ee120aedd5bb909feb9f8fd1feeb0dfb208ff81867d1ab7baa43e8a33b9897fd028662ea413bc956886762b36d42701f902e71efb45fa6984a570695fb77aead3e36ec940859a72083aa95e623dcb2237226064e6bb25eeae7792cf39e05bc276aa11030bde0351d7ea1c173c20cc0eb15f08c7fda32dc9f5ef3317cb582fdbad3165c582508970817c685f825cdefcb7d2a060bb5e75ce98a330b3d2ebedb001201d4d763877202b10f9cec0381be0517ee2b2da2b9ab590c9a086a557b6d94029278cfb09f78a09d4967bb35dd2dd26558a1b84754fc586d95245eeb6b88a1dc4116e8e9c84449bccb092a03d348742550fb043999f96323756de66e4e149f88b5501200683833da67c6eaf10dd0d9bfa9328f198a6eaae21eecbcf86f90498cdc8cee7c0028c6fa1d4fd3121587603f90b986ec8195be95b499f5236b50d4b47525c91fd0f030bdb654118a76082bc0d892d59c6f70fd9d2c3c89d5f52e83e3fec06a76341fb8c0344c583746e466a55996826ac4ee4a026e892a634ac474c23a785ba215a47935738e66042b6e47bb7d6392e16f6b313ffe7d2ce251289571e064ee2d59e9d98df2bb2bf7a96aa162c512e26ea58ef62700de678647b890edee243f01954606fddecff85f683ad21783c85926c37b19fd292ba27548509abf570fa30640d7607420567cff89b2b54b69a9e4eabca0cb5382d41ac54118251a64a91139d2e10e892dc856ed2ab59b03488fdbb9a914c3dc12f8aa6c8d92cd237e4cee415a5a131c711b8d8d06e2715286ae3aa11819e881cceb7e9f151b59491a821b5f5c7d197575b5f6ac061ffc9beb893c8c3b0496de0ae5fc52f36b33880beef898df77db26bca45eb866fe64a107f481d03bc2aba065ce17604dff109eddf713de462037ec2c3e6919493b600cf83c84878778697764437f1db8f88b1cf415d058c42d8d97134ebf5a3fa23f52efc4b245ed984b1628db1e7840c9f1c22852ca4fe0f5917b30d2e42a1ed54785cbc8a5137c306fe0592c6850e48e996fa9ef4f28a1963b26c03c6eac9b7cb5e5b8c86b3a0629517bee57e454fa4be6d1ae471abed4dc4f0c9b30fac18b15e0c30927ab0ed5828943fba109c03d52cb98850a41660122e019cccae1fbacf156be8b1faef08d94618994d3d25d32d239f52713be76d2bd5a0594648cdfebaf2bdd2af559c44aece21d96cdffcafb93b05d06c33b67653c2d520f4ae2b7bc09e0bbc2149b408c40f8bc6e29b1e6a101f7a7d10c313061f7a03d78704b4b81184c5d3d32387095c517eaed2562a1ce4a197d57728a9af1c84ba135cc6666e5d8cf9db606fde223f33f68f660a238faa93998d79b5779cc3aa5affa1eed7aa89b9c62bebe36e9e00b0a553cde7c193049d44f92e4e0fdbf893930f78435e7694b76864ae1512b39ae2bcec677a8af0a87ff7ca1b7b8bd15a5003aa4c45138914731fd1ec3146c2594183a23da97fe415ba4c02e2a9b3c909a297dc2a2df1dd1db03c221fae04f61283b00168aeadf2bd69e2f9f84a58bbad0cd0f2014e7d258fbe671c1adb32ddf875ec6d9530f891d0dda8d2c8d3f5d23d0d3867887631467820e7247bde283506e9c58d17ec5129233a8215e2bb4ce7fc19d13aad7dd7a3b4fb7cd885220e7d8d5d06472920ab500940e1d7a981a23930cfdb596301e0ac7fc6d393c4d6bad9f246186fb0e7c0066ea65acd19ec1a5d4ee0694b5d79941b26b61d77ccaf2a2e4e4bb6084809d137ba58f288a51dee05a7b83e70093ba6e12f2d591f0b7dcdb41f177049457381847e287a7c96e2119a1c4abfd9a318adb0c05066de62718b616f383119a9a625d0a43d4eb470d388ac160c3516e9acb7ec7d7d24cb23daf333b962de4351e4c942477eede818097465bda60ab41ff3239eb196fe1a619bdb8f3bb20dd0ec98ba7b643545bbeccdefdd8e660fb2d0aae835b044949825277db3e6023f89ecbb7e5b8aa273071e68b5e4475f438f9ad04f0f3e31848fa578187d104737761d76b36714d13ec935ae5813e3b391af54e468318ff0d5df54ebc911b83d892409734008dc0faa2899619764218b9fdc08f3f3ce2a8935748f7bde737c5103c0352e235bcad82341ba9d32f94d04de2bd9e1a8ae5ced7641b099d69c62e8ca599f4063f945ac4a4f890dcc2075dda3b75b7af1dd1c2bae8a1cf1391e24c4ef34fddefbfb2401f2c13380a6f1d77f2a11e3c686b5fe5f5777197ad64ca365f809f8dd8be31ed8afa0c4df52608fac50cedf8f245e7ab52b88d056ac2d11e66b13a6bd7fbba13bc21a140c8c232b1e3ec2f6cc63041289f9beffab5d940d86322dbe488a25c9ac87e0b7974cf7c205caf9f5267eaa196f13ab06127783ac85c5e330b832c10cf8f79da0f617585ce174e0f9185a41f3a469b976eafde735467e75c213348e6c21be42b38721a35304e7f5da105b51742cb7c51de5b392dbe193a3fd7916548cfd881e5c635e989532be6546f570f3867c0ede53e466c92422969460d75d76e20cd8009dd9cb54fe5b45bc2df41a70ac1942f4b889e31f7c621052185e6215c6e4567db1bdac012b5c1dd6e1735e3300980329f5303a26a9b820e04db48bfbff95d9b818d6e1d57ad5f50f0a5b69b2eaf2ac2d18c8a69fbe57db8400afdf16ed30b96eb8a180ef20553bd0655229ee6bb7cefbe8825410d7e1948fa7c2f382e6e93c439286d901c9d2ff236137f51fcd56e0575466aab632ce18f8e6004b9005c99be9fb52d4e1022687bd39eea586485f00a6a3bf16b1d393c9bc17ca3b9a277f4d126698f11890d9720820f460393ab8b75c8843a061c3665e21f7099b01e99f4dc7f2c74c5ee8e5914f7ed585c2edc1821b8f329e4202671f2cc5ba08cac687a5daffa49c635a701c181ebae431adb467c6060fdcbaedecc798556c84630ed76257be966199c758db125209124529e3b5d12a1467bba80c6b930853314ddf747c3a2e7f3d2c61ee35a3c01b1fec64603b413ed42f06c9f791f6b4ecc723589f930e7c962127a69022b13802a382845d64fe66febb84a2de2678719d91a2df7fb07a3a86426108b47c92af57e3eb979bb8d0ee19d17158ddf089529921a879660fbac0c869de7bf0611defdfc2b0d59ec93c37eaa94c71b674cf0ed656a246cb6bd16d8b378476a21d8b6884ebc875f65eda01659bd80470cb341c4fae3525999f2dfdba2f921fbe67001711830404d6a106f3b614d434ee408b90cfe183380f40f180efd8aee011ac85d7defddc31e7a82b9090b829a4051bed5518fc5ddbe0c7a06a0fe885c162fb0e47f8bcc56db6505cda8541ebe55ab15cb503289c3c0751a76498b0cb50763bf509d2de58edd7e60f8a07d007a77ddc9ab2acd73bd02090f5e97d0f4dfbe3799d5c560578e747588a30bab658c8e4398a2809d865b1540dab39f99ddcabda14fb5d0004914a6e26eac870d460b35cf9787df5bd14df0c8a00f606d57edc46db46d5751bbc228629fa3adede2f610e96622f03342543cf8d07f5774df099a364209afa7af75175eea991fbfc2a6caea8832fa511c5167e0d84f07fafaac07307c9add7234a16678d52726d995eca83cbda8da5d092e9141939d831064e8fa09f11958e4cf4f9f3a8b43b6af85110a940c5249f9821bb4b08fd1f4ce99d2552bb0efe2ce2f1c06d60a11fcaff656c169af988b03ea0156bd1893c5a977678ba492baccd8d6f3e5eac378919ed848c6071504963711d070f74e626be197301b6937da55612f26e3d6e4564423a07a66a7c84a98a5e4550a5f4d3d324129107fea91bac75be6ed12060fc52a3d7dc23762fd31090363435352cc9393760a62d66d7a9abe7279239a18908e17fcb586667169620c633e150493391f04ae40d9c8c59e69a7a4e84303c27ca4c5feaf7a9c3614c8dd037036673ba5e81de64a652bd10e210e1f17659f24e3bf41ee6236e255503055f87b84a4a8f4188e08b9f4db1fa7f64906fdcf1cfd8f99624f836ee75a0735b97969072e9f5298710d29335640b7a290d0667db4b578f24b1d5eee9f199ec58b3181e74b08f622f38142dc8f6c297f512d0d3f9ed6e1cbfd4f99b93687d9a2ea01aecb28dddf7cd1f72b014d2ff99ee183570320ec25676d792b642890a7267fe36c6a3a08dd3d5935fa1b8eae99223b7d20afe12f2bc659a68dcaf2043451b564f9d591ad2e2d5d284a3df7ff7cc435efe0fef7231bc5add7102c11be554c7c8f89f6fe78356e80d7488e8f65d00e7d70d215e395d2bd102e3e1ade6c7545e589315ca7c4bc737548c227eb82d1a0c8924c055e3856b1179a398e39e996de71b7e59a246d7b5c3178f39187acc2d39066c2c60cd7d3aa8980b686d09115c9927cfd1ca6ee1f7bd62b90ccdd135c096336f5cae48c5e75184d6fd400c1152e33cf51eee23543de6e7e48fc2c600575c9bbda7117af71b41ba9d7ee90c968f66546eb9eb7c298c7f6071ffbce81bd59e5fd1d4e45be2dd09ddf3498153510645c0a6f347fa3b95dcde5cace13ca4972bb963a56f1ca33bcbafc3c4d9e6d1a53f87170ec9e94ebb70bc805769e8cea085f1bb4894fe79db5cd7eb90632a836020bf1967e648754c55467707c333ea371d4570c80a14d7a78501b608730ab6f06188bd9ed626d2b939d47c2b2e9d18724db89df671c2576bad91ec54c1c3a7587147ddfac2061cfa8db2b1b3bc24fc1aeff75dfa7b9a76f5bb9d071fa17ca26fdca477ed2c548bd7e21f3c20a90adcc1a5c5b9735c40619b7cba7ee57326169c283363d01b3cd21335080ac2455319d389ffbd85cba0b99920c0df059849778ee017c43194cd4971d203b1238f2e31b1bc27adab794a187a38db2b201a5f1542a7c3d3d8a9181d71f621180d49fd69d5d49262ec09d0cfc23cf9c7af97889535d736c41ed33b4b487d4490c77c505709912b61dba31bdde6a0d690c28dc4072d16b41a98af33e8db9f4e607aab0ad21cae3ae5ef5b970e5eae1b2db925f8ea5d9ce6fbff803c350d53176626e67b0199d08997aec5d8f3f35341bec0476a90ff0031431ac7e7485e231be4c7c8cc9fbbb4fc94e27824b3d2bebfbe7030d68d0eb86eb9d4152a41281571eedcff8dfec0de2368df95bd4373b952218349953dc2839b922511d076755c77de3f18f3eebb6833f1454ee49cd071b24244a18aa89738b119a6bf158b6855560d30426df38a5104219ff22fc14f6da99374e5227135ea1eeaf07effe6b87a3219fd0ebdb16153bbb929796086b7909925cb3d26b49ffda4f1e66ee9b312b4ddf9f81d71c401d495dd1a57678320b7dc83a199232678981845c59d09ddf2f2e534a9fc3e000ddc330a36104e385abad5964c2db62f15b9dcd43e76fd02af0f60aa12fc047861142e800c7e16b069616eeba06ad730c7f6c8521c3359b5b6752ba8fd0d030ae45c3681f1e5fa6f7cfd0e2fffd13e0c8a6c23bcbae628791569206df1ea676cc00067fd2b4e9ad7d7ec25c35c9ec21c7a89609d3347761b9ae600c21ce3aabc45b8ef138d6658162041e0d1f48e5a8d5dd6321ade451099219d81031a86df0414c160511c5ee8c083e75fc0298f3872118ab149b426fcef28775e85432d3d19db548cc9548f1e62dc3bfad70445d394c0254ff4337d89d5909f4cc1b6f8aee5ba1f9c72d3f4709c671e87ee67605835388b6143f3e48794b4a1afce16cdc41ce9b52a3906e3a5317673ee9e246728be24a31897893271f4c92a62b0d7c12437f0a9b823f838c8c1b40bff7326c0c6d375aa12fd628e7d4a82a7697fbcd51104b1440c3e37d5170f14b92ed27374ba58d5e08259d0feb8960e8f4020901bd62d193798e7ca2354edcc649d29b08a3e23d40869daac1a82bca199f52b2f98e85100a5f40d9ae7acb02157644f9a8d5bbe430bcb4f5671b80739f53ca4339810626c297fcf7760d4d36573c0840c15b657bb7ed46575584d5fb4492f1055dc545b09931220c00e368c0cb5f0def7e443f0078925a9a35b8146379ececaf042ec52cc60d76641b70e5b000c1cabde2c68664b03ffc295e30b4943bc3121648b1e0aa26c90ef386b824c4ec91cbc0c833c56557fe00bf4f30ac2957be49f49b15f7d875a3ac9afbb44b60fa041d6a1058342a1e6b1deb902e230a3b9f91c9d746be8741eeaf0078cf8aeacd57c646d85b3fadbc8733f24cd26b2a9b6902c4b82b78d67aa173075c5a241b72ef701f368f315a1f726d8deb242b82269163a124e96df5eacccb5f4f91645e8c98fc4bd1ff49a2d5691b409e4b55d0f5c551180d7d4207ad0276af65205294f3fe2b0be0190629b36b85388819f0382a167051391c950c190d7740b85223d4736ec4c480de43dd1020098deebff35c0486778ec49e9f90859e382cd3938d759c9cd43379b4399c10d0a93ccb4a3b9cbbd41f77a79dd3e29563a643dd071412db6be84ffc1e1542ab40e5e5a6715fc60daa7b2e736df7300fce00efdf558bf27901247e9c7d5a4d4555b66e22addcb62ce89dc7c103202c1ba23d9868f154a5db75b2b14c1490cae6169c0e6ba1a2aa1da6f0f6a70ceacc5995f31ed2586f765f172af9574f3f90e42bce8d09c830ccf2ece2509d965e7b486261ef814c6fe6971e975b1515f945bb9afa4d455ccd14b3e7d871092fdb3aee247d03d5d3cf87e282e515960ea7f6de3e5a78677ad7a0c9b56e5d1900b177f95ac15ce1b94cbafd1a795a1aaed140004ef4fefa4295aabbddd3895fee6a4db6b9fde0da4095a351f80f1c1e29f6740e9bb981e955842b7df8fcbcd12f97116b178b4a457427bf0b3fd5d64ccfd26707ef3ff099e434e9f75acbcffee60f9986421b86ac36c14fa676ae7fc88d42569c9297861155558ac0c19b77f46f46d91794a4b7dd0de3a1790f14dbe57889c23668ea524e048962a7cc9e6adf1430ccdd187de45f51eff47ef8333c8fb72d13a14f8a0201c057992a6bb8f319d87aece757bff66aa459ae35ac8bc8074856d708063fc67958caa5ebfa369a56e39ea66415f398509c653630a2cd52818a8cddfe1ecfaf2644c45244b2685b779d0ad354a4ae0e731abfdd30206c3d4e641dd19749d187ee5d66340b82005febad0c327f34453460038a4cdac069a1337183d0c765c186dc3a3bd69bfad79323a66568062981052847edad8b229fb4d945c33bdf34812834cda6d927b285db491812a5731fde6f71dca61566017ce10ea861f28ec52538376aadb0c985a1e9c03508dc3ffde7acd03eb3d283180c3ba2b900ec1897bbf8a1244dbb76fa156c415cc8ae156dd8a862af8412c0a6a7a3953cdde478a0b126ffa3ddc5a85230e1f09fe42a332c3f7e02561cad582ea2e5beec58f5fd7c229379bb0ba97a92b251dd520582f26a551f4a781386ea195d3bd832aad1ab2c153fdd095d511e242f64b01e4029d20aea4d4614bd1c47527afd5f7ffde7253cfbfc1f1a1dc469640281ebade5f62f2972affed46130cb3acc976ca8e6d89bd74601ef63a7232577349d1e2fb9a22496ef5063ffcdc4c07b2a5c37a7a3da1bbddcbe23a65192c1544a043a68ae479295e5607cab5806438b2839a46a1079c326a01f2e88bbda9e2b1a91d279e72d255e84661afcf714ddef3648bc778952a6a8b39b78dc4225d66c4db402076d48efb7c05a1efcf63872eb851d3c53fda8489050d4aa56cb2e8387601ba06b5bb153a98b858ff9d83159596c203ebdac8b39fe82fcdf410c948c7fbd30681f7ccf33dac19150514499fbb825d4e44caa99272ddc402eb19039d6c1b37d3c01201dad409290b65c41517d455b75437172d5f4e41445cc8cbd4198d81dd395b04a2de1e0ef6790ff598b09d00de23ab77c51ef7792c59c7fd6c490e95a98bcc1659d9a58b582a488abc7993009bf4b65766299b9261fb1e1b3909df355843f1002fb6d7bb02b8b4eec0f6b7c4de3aa1377a5f70a0db200cbf64546dab31504631265b345b0eacd3301675ef9836337a669aa95dc29c66ff61b4b748664be92dbae52d3851c138ff9ce6f49e1d222dabdb5c751df5bdd173f76afab30810570abbf96499004e911a898c536ad6b2c518686324404a89d026ec5ac94771528337c7f56dd5cc70e83297b649f8cd44b9912a639c336603e417191e5d9215ec2044de838f56f6ee39decdbdce1ba7c323bd5a4cb6530d283885396dd33c93b49ebdc7d41a517a9aa08c6be985c163dceb7b6d506ebd5435f30bc9e20b53e4df0c61cc5144e4f0e11a06e8f7d533193bfee0e32f0fcaab582d32914d51da87cadb69d12afe15c8f2ec583a6fabc85ad567513e4ce6af6b341553218748e2865726d5612f51d8123aec6952f8c7d499451af1c89100b1c415ba9ce7d299fdf03cf8cbdf2a8230df73e377541c4e6ebd55494312d7caa1e3444f38324a1d02021dde3c6149c840dfbfffa039378bf3fd0562cedc730c618e8200f56ea630b468cfa148e9ec564239010655cffa8ead8b3137f74143864a024e6f49e0ef3abdde65d35bf3242ba76439f31de9e03a9fa4f0a59dd9ca7539e3ff4ddde7aae884e777f230b496388203cd1a75722a6137eccdbfc1fb0af508deecf3f6716fa70aa1f83cdf708cd9ebfed723d0fa93038a558d128f0323911dc845f8e87d3e96baa2d04e817d2c03760e0fd2949657918bfef939bcc3cbef0b2084b6bb819c2ba6cf4e363a51665efe8adf33f96484ec807318c5a609578bc107e076f98ef9e61f854e9b0c1a43aa6c9d67f1aa51986b5e86edd022fa911bc6d1cb3e75afdb175811bfd415236e4c7a6ef5525618926c710d4af7eeaba2e6955494a48b6b351acf0663c4599849bea682ad77d0d11f1795d57830f839203121b8173dc5602a60c03b425207cc12d8a0540cfd71442cc70cd46654e6f62ffe4fe009fe2dffcfeab04b7581b30e45b3755fef9884a2c7e15fd2004739db5997cda17ff571f57ab254086f586057474868f922c62d895bc65c204b32af4d8e31365ee0d8b5374b349819a9b5ae5c61c1d42df9f6d1d002c00dd54d3bdb0369ac11e337f16b203ec7442b983a3fb1fb634a622cf23ec7402130046ecd48094f313295044d71786e5c97fc97898a1a747adfe1b2a39a025c77e2d6099189749a3794315eb2fd9da255cf849b627c8053ec8cf1443c9c904ae742428ca4b2ecacebad8ab292a38fd21baadd828e664664c665b5f6de73bea3255bcbf41fbc8eb46221550c4e66d8065e2a968d7b5f9bc3f07e0998e8e0aa9e046ce2ee440bc22006a40e88261e15c56104d95eb9056180d1e291e57f5e3b9da1c8ccfc86e4359f21d28a4d145028cabc9a8f77bdfac4fcf4c8dc455efa37c86a793019617a40316e329008bad3a1f8d7d7a05e4f52af834539ab0b1b2e5775c8b013ee16729fe2113f4fbb69e95d25deaa510f7f7d53597b708cd6cdefe6b2b2f500c965999cfb048a8a1f52a3a4f0773fd5330ddb343163f78e6f7d2a3ec4383f3bfa9a88b35250e6ab691d3ad91a20c560515d50d80c7e345e48dd0405a914c8a8b83d1e7c8149f733d4bb1c635d2d2854c7bd6ee7c0218cec1802d7ed22f754e655ad9fb6c5c107cf2448870b2ece4ee2fe83d7e6c977d4e1cb3c3e4cbfa8c580e9cbeeeedb79611562d722139614d92a4590fc7b5ce4678085ec7eb54ea94bce52f12c1bc664583a15841f76d674393fd7d88e637307c21248b39ae0b4b8a0c76aee74ac1b24f636858805f7083348187a7258c7b85e23faacb70b047e325b67726f04a816c509d65b1b08269c64b054b789c44946a37707ae279fb01babae0192fce214a94482568e15a96863530b5f8b4bbdb0c20dfb0c52ddd05f96735e5cccce0e8b036fad3be104baec0bab5f12c393f0aa1ee6651dd7b77aadf3b3838554db4372fbe4f8c58e04040e794b64f4a1d3b4d252d634ac34a2679e827d84f59343ffea440fa4ed5bf4be1b0dbdb2d7a46d76dddce67a7babb58dca83ffa1a0235f0d8959162359601dade2e91aedd45c138178a1f42ddef46bf65b34ff277aa8b469ad0d1b141545841b10980e68970ae3c5dc91b9aafedaaa7515b11fa116ac14a30d3f8b71bb41de375f9c8c64d2207009bb193ea22f5e68626f329dab7e2613eb5b7304bd0e6c646db2361d592fd36482436e2543a228db7e2bc0db819b5a8e91c66d0aacf766aaec22bce3a2c6009c2de4a257c28da0be50dd2860959edcd67e1138c4bddee403dc45d640723da04feb43b3bffd7e5627e3c13d7669d611276c1342b0e059286eb7ddcce7d71e2a8ea28b6c218329b65d2c425de5dbf7bb64767788dfba01334848d5aaf23912a97652c77e6afe1ab9d330e1a9c3ab25b0b84d2dcb72833ce6a9ac0d83a63c5b34a29d6dca54ed8078f425ee177e161ee851795084e65818119d87f4d66fcbe90996f2a2b6e36f919e42686bf941f1a2a669d8922b2b111d9b3db97db417d33ac21bc4b681bde8a2fd6fa6a40c1feb47f151910c6fcfe2b736774dcd0f77cfb6fab6b0c7aca1a454fae82b21aadd98dc217f2398e123ed52af9582bc185365182614b166a507b5b31c63deaf07c3c1ab808f1b2472030d6b8c5b1daa77e756db081db71ccbda00f54b0c7fd89df51d8e140ccbc9dcefc6041aba82b791b84694b02fbd14f1816ebb422062250ed50a7a2dda4f895d093350222cf0e29e3214cfab853ecde2afe45946f1d712fc31ed000b8257295df4537e0cb9332d8d8a4dc6fe84ec65b7d7d68c3b9706371dd2644d67983b2a5850fdb477563cdaf0a0739f5475c8741db7929842adbb6875da1cd95b4c2ec55d5eff227bd48cd4018fb53ba5b7ebf75a75ae4d21d878e5dda89877401c7062efde5840a16a3174596a2eecfe2d40c876b7ac3327a081266e597dce0bc6d5d765642f359a0fffe643a3e8f552ef284deff0b4e19adcaf9a3e39732970bec2c54d3d4280e11e9e2feb3de60422c765157ddfba79888bc94a62803921af592a9e5c0564f0a242ea78f56705b03110514d72d46108de79117cc85f83dcb8750bed17eaf64e19b3f25547ab0c0fb0205d1d62a9d9c7ae2c3a75d28ca20692365a70ac8b8e2ae3ecd4d39137017a29b41ddfbfcbf8eea4302da99a9af4928efd767d0e1cc920b4aa435b213d0c6d050057097a71a03344d04a791b9969e3bcd4a6c90180b7931d1cf35258333f24cffe622ee89ee3baf0a0a77df64d5fa674eb2e84a44fe63183f4e1dcf727fd8aeab2032c44018776589286f6ee7a61238921b23455bf9d8f2df2e31fd86018371c58879c5701e88e3c313c23eea20c269536e2a05b29037e29b5ba533b7009bb9f2cd5aecc806bb7cf8a662483a18db2cf0156b869a604b94f1a7ab54da3468c6f51aa58dee1271005ab43bc8283b365767072bc1990d93ae3b78ddbddf5622c132757dbe1eafbdcb7172697ca5cd9b82e8b1a94f04654d79dd66ae9f4b0c3b72bff56d5f442ff5869530925f5359997644c9ce4c782aafc2c535c0ef724b9ab67d82f16fa540de7dbf658d5d427f243248ce9d44e8c20bc1913cc7025ea3d02cc10de8da1784fdbfddb08735cf585a9b0b83d752f77eba9f5b50e3ea2f0e3aaad119e524475c68e818d9d82ca361e65b9742f1f64db5eae886551eebfb7ad6ab9ed44c8e55d9988f8f893a5b4d6b389b398946cac5b99fc23bc46039520f2650199e20b979ddc14f115c6f45ce8771b2f9678f0309f8046e08108309dd1960953d47ca4c2223bef4382ce0319095a73bcd462e8bcf812a77dbc32287e1d12e4df72de3c21ff50ce82491ac7d0b6ddcb8f9e10feb9fd7afc00fc04a82f35993f444c504a931184077e63990a02c92bf14bfe649bbcbe288b583fe98e55948a4cc6d04f4f292e422c212f4f36f5af573e4a68a38020f6a987a0cde9144bea798220e4539e3f0971d3dadad337516ae16d528db1710c706681dee217fe4deeecef473ff8a3870f0dcc540af66e9af4599e349fd721f608caf36fe66dc16e8e36fbdbdddaf0acde585e03587f7f922f8a1bb8158f17059e9127d50f8668703246a43810dda272e3d04a1537065a6c6aba979c1ee8f9b2630b261ba56f80015b3303518254be79d514ea15028fafa2bd893a2181614e3cd9bf8559c4084474599e88f564ea3402b210c0ed75da2969d51beb8b28a964a5dda06c839add9f19f2a5f82632f08766029ecdf600a5baf552b8778915af82a045fc788ba2bfa51338cd03fd7fb42a696ab45fbcdc8ff4287682affeb40e4672fd93b059760f44101369caea5753bff5f91c35a8c771b9967d818ee6239d876dda8f7022327ab156d3e9941a80aa7ce72fc3c4e27b3100b6b5fe16241e17af5f862928b422e659f0ebde4e32c9965e0a89de193a067fc592bd3275f9102de5bb659b9543562fb214f3bfba15525275c1c3c5bdd7c23930532a8b1a6860e244927a68c402858ccd1dca1bafcb99a9f9c5b8700b0500742648f1989566688cf7197dde52fb8b2e0ec99d25d3f1948a9083b529f1d23f8479d1d350d238ea74927ec8a278eb957be0b7211416bdb4a8328d6fc0e9ae546b074855faa52f57cc4ce9fd5d3821d4a02c20670d1135446a1a01a8663fdc05f868805dc6482a326bf7934c720b93c638da1e8c5dac434d3b84ef67253124f299d6517b19eb27a7d1ad9a965cd467db7826929232d5653a3783e4becb637b5f0c89f102a6052dd36ed484011307d2fae409f53c29aee8cb9f3818ba3e6045d393f28e8c2059f29843834b0f37bc8e2db9e9ddabe9d9dff2b258216ed95abb88d4be43fdb60414793cf0743ebd322e811eb53b7ed21bddfd39101f077f1ea39f2229ab8c05d7e7e6f1ef6cc3fad49998d70c340389ab8166e25b24f32a54f9a55312ac0a23b47b8393ec6ae3e60e93f018a87e63bfdf8bf2b88ea8396cef17fa90ccc0f6c72361372017f0a4bbd690d51094fe74c6c8ee534a60960f4f2b6e71cc662483fda8217ccd9eb43fb27af84957f9d85b3add666f091429b6f8bd2512afd1e0e7d0b6ad149201a75c04b8e88a0c63b097b87ae2d6733c8c8b447e576ec773ceb6960933f125e7cce60b9da0014826a60539db393ccc302c8d43c933e45ffaad18473d1cf4e87034f817c414a4be4edac99a223fe80710fcec4f7414a39c57696423bda3afc6d7df9b02cbc835c4f350a14a2163c136b09dc2dd37ea485290a840faf3d54906edc39753be670fad3129d4dd91bc7792cc77fdfb5e9584ecaf078b02ee51d0c59ecf651e28dfb9cdc216f0ffd6a81ae8757bcfd6acbac3d87e0be33a624b289c542c83439a981e59640105412c5ec22cf359242140a5523c4849daa357e5060ec915e200171cb6899449667da708a8e9007709307ba71f90c5cebdb8f31e239b4c63b4ef7a7bfc0ac1945d9e399f3c5514393a8f3f66e8c30131c364eca45c69a5a3377d5f9b9dc66dcb76cbcb58443f955b5d635a3d65969e7fa5029fd108b292bafbd019436ca06b0cce7ebf0772076d86c9838030ae9a0395a730f2d679e8bd3bd5cc27d2d61bfbb4924d4fb97374c1c2eca53ab10fbc1bd9d5493377067a5f219d5cf7ccbb229698e59ff86c2ef7800ce621d06b6fdcb7ec653c02bcd69195b877d1d7cd9107195c5e1c5461cf9b438167d0e8587e85b274c05e24d13bc4a36c10a38a1442127b265501acaaab868f68792bae436cc72bcf54826a23ed3380dbe46bf5c483685633ce8d9d7a185a3fb13a8d0ec60e14552eccb0bbc81b71f862cbceb4e9aed69f08e1c21c8d28a1e015ce211f8a2d5319ae24cd6ca06b135c1b4ff3642666f4e0d9ec34f39848fa6e61e89b7b3e1fe8c84d98dec6cb930327583a09938e42149213105e8db4eac9ae2a2f78b98dd279500ab8517e3955519051cb3a6c5510be72832c5fe7516f3c642ed25fec30ec5fa4bb4b93339ce49765835c90abf188f1ea68d9a12c5a91689f0ae6bd91913c8a600da98e9c2ce5f0517d98c53018eb7355098dbb178cf4c1a8d2b618decf6201825b3505ab467be783ccd8b2f12b70107c499138a977457d97d740a30897d90a9f39d3681a981aa3d3621dd7222c970d1144eb05e2757dfe0a1bbad597f1aae68ce8b8040fac56c49b9fde001e7d7eb71ee47e4bd020238d7d0b0b62c63d7c051af29c2314574c54a846f852ac2fa26e1a57bd0e208cbd6ddc60079f28e2c1c1461a2e405ce0038a77d1b2e23d058f0a72d4fc6248e620f58dec9620d09e722a85c8f6fca631223847ad637585f768061359fce70d0f4b93b6232ec5c3567a3bc144b376e786bceef5bee3f1a977f7bef35706a7563633fef672ac15678a5ec3c054bef119d2cac0cb8e3e099162cde50cf2e319aa081501766ea21330743584dd0ef3ae78418529257917acd587df20661cec7858835ef4c913eea5a0ba1f960e676ad8ab9ffe3e440286abe7c0eec62e12a51e47dfa7b23a6d935f83ceb4ce4466edb9b36ce54d787a06f733a94dedb80f8300ff85705c26b6b7d52b6f11919f44f77af4eeb971c4963c1207b3fa112723577e196c4aeeb56cc185447885ada257e2ab750962d4332595d30e7e017cb243b4b742a56f376f387197350c8d26bafd647e8f96d53027915727826ccb319a7847cf7cb3dc4707ca3446e9fd2418efb0d24c317e4e916059fe009a17228b83d3828ed656fe023a6d0db7c09e0e2666d1630f434711de9d2125772c52481346e5521480bbd67369f8292f7ee81820b03a6ca9c9a01221e6848bff00eaafb2ad602b2e28b29063f2707c90895aa1cba107d5b87efc133af8d2e14cf3a0f534fec196b8d5a1e0325dc72f337d230a26c324d0f164e865508084a5471cb54b44545794d731e407e4a5e86fad95623f1c22fec90b46f787eb3656b5d8020d8ab9f88e8e344f485342a17396a3813f70e683b53d2605902ce8246b4176f71cbd26feafa1aaeea8e1f238f30321b7dfb2dc48337e25af66a7d574074ca2f43444ac43918f3839c2a4f4e1dd39ac45925067d9e5aecacca2ce38de050bbfea8d38c2fcfed8208bf5076fe8ec58f4556ee140758c6ab384ecdbb9419d75eb1452bc79d4a9229704d1596f0947f90b43d5060818e0b2542fa8adc1db4e9b00dd4d86ceb1eb0d38a5a3824374618dfc79ff1789264a83d5264c162af4a78d9f1947f3bed70ea9525bf80ac5188de59a30a99a2d686cf8f0b858502aa6b09db9a6396b91ad2b4465295394f23daf692b0412e090cdd467e59a7c97454cc1110fbd95f2fee88c384f5c25f21c1e2d3d474fda7617a84246f35ddfa382d54ca8d3df7e272e7884bda67d4316b7766ad39e6128dbe62cfeff7668583d0ed8259266522f6bf809d79de2480116915be3995f22f928acd55cd9aa9eab3a38fc127467dc3c8b4300d2d1083e5329b61486088606dfb59236e6d9e849534ef98c1c348024aa258aab7eca6bf5db9d01e71c983b51a319fb58c0d2da3ecb914a5e308b0f0aefab02c8347e539a8c885d1174edade2f8d6cf11c31713c27edbfe6afafa1ec8e13b8d9e0c943a701a5a780a70a9ec96217ec7029c1aca3d9fbaca3bc8b3e3c6d53f6cda012f2c2faa2d6e0ed5cbfd50dc3ad8d50b9c22eb73745f5e4adca035cd2f394c7d9f80a5287042054651aa64713947a631afb9b25365aa25effa1b2fb4cbf9d1fd589b059a40220922d7703ee30c5668193e6546bd0464d3b205c1c00e075a14a1ecf1204818835be91806e3b37d9fc6a5a98b078a32d274d2be207ca4905e6e8b83f88ad7d1aa5648086d35f5a0a56ffe53c0d15c7845c247678b36d15518b8cab9671f46060d6d2cf236fe3efc84ddea343a011ca8d83c436a8f5bd21aa56365c6e43aa73349a35bed78a56c75cb84c4992d1649e0ff5001a20f09ff3929b5b8756a134bd0cc6c748bbc736dc0c238eedef9716da6f968296c3bf5a5c3b25c9aa25bcf60c2f70715f8d75f5da45f91f164742bacf7ed626060a534d0a6172b0c19ca52f8f12a052abe62438373241edd6f928050b0e52f4af7e5f6fbbb29b08da85bc884847eabf295ac0b78084dec41c16a4719f51a2ebdd12c62f66dc6315a66896cd698bf4ecf0a2cc0b4405658f4775a6489a8721321a7b527bb615c4cb05c9ce3afbd966fc627e7d3859afc261e7fa0e2a03ef165c03ed84894e6e8fb057abaf95fdb3a0e6200724a63de68f42a61001338590882389cf2342c7957cf3133c2aa67cbed2e8bf26d18502ea87c7c326b56f266ec4508da54d715f383b485ed721d0389f85fea06a944d8d413266777d1afd9e7e71a9bf517a527c1be067d8e1d9d43c5f0cff210919ef7d2ef7a40af0b4bae4982c8fa79456b49f5edb79660c12f57e14de7a43b17cdae0dabe619033f2248cbf5e2790a41161f71805f5c028282475746d58d9c91409e77830eb86171a0907b2dcaeae8bd612ca36694c649abd132a25bac9e0451b37993f21995c670a10e1827aeeeaf5a17f3ddf72121101bfa2ab6c2fb9435b3e73eb2731ea8ff8871cbf0f2ae27fd746c287cf2a41123a5c0edbf839052a8ff19ee6ecb020c2e92d1422ecda7f9eb6936738e6014d24a75c655665fb62ac0533a2f9a987203373ddfc46c2e04fa35a105052eabee000ae921815dbe322e9f3afea3b4f4bdbbc94dcf8b4eb4ce4214e179f21ae01d333b833c22e8a2b7204553c9cfd0c62af086bace99548a32d5b9c5ff57488c4405df380e9d374cb9ef0b6d745972bfd57decba5c44f905a552b4c6cadb429972f6e649982ec2e83d33a9605b34abeb3f70b00a9777784d5df5c31c5cf5e8bc2740704f3b28d13c6b467263613396247a0fdaa17b3bc78619300b4e831d5d1d1cde30b2ac758a29719fd60c97a9031938168dfc5475898743b2a6096681f3369034aece8e019d07ab0939620783d5b8ebf8ccf981d8b4e5d55be1040353f93d604039d66fe8148b81f40cf8ea5f4e618129f2ce60de8e716584fc5179bd6b9e10db0b01442ec3e8055d1877759a0c00e0d5abc30875e4cc524b207c7a1987534bffdbec333b696c13575b530067dc53777480422cbf742a951407514b23877870ef53289e2996bac2b2ca1fbfb80c22921e03a2d0bbb5388a5bda8df5e90d14eb20a88fd38801ffe1aaaa4dc3956330db6c989fd96f953b6d96e4f0a587397a227a84192f311b83d9bfcc678030b8ded4205626f37d5aadfe9a3e8e796c65ce8d7210ce2129e706d92e956b3a47b130f940c5dcd5d830adf39f7847e86ab5434ba1a1c55f28779d9b3cc0d7aace8ff797fd5bacf9bdce7f2d9e9a62fbb9e8fe817dd81b1b658b489aae09669c0bf47c6c9c8f06489cd6574d39000f12a6f4b1c93d7b987dde24befe3a451f4030588bc8982dbe6f2480fe278d63ed879901ae80b31e924fe2739bc0f987c1be1eade2df31546d40d24e869543519aa35576cf0e47e65294734de43a35f254ab461723d74412a1d6f263022ff65490225631785d4f57b7fb1af7bc96f24496175fa79ffeefd513d2e56165247a296dc9e944ea92d4ad4739dc85c256b6d3b1921f56db6bf021cac7d943db6b1de0fe8f27fdfdee1d73dc3b6a035eeb17aeff3b0d65008578cca08cfcb291c4f56de2bba15146ae3dfe2be8aac0ea65ee2aa0772c5a140d7a3af1081ea22b56fe0c54f8635c7a2060f1d0df3ac3178708f3864eea2258c18323395ffacca602f69d58d16c9625787f0a552d959d76f6e841055e4a650c84e3a3a444f2edaf02f998c1062db910ca35a5e4f17768fab9e1969ab214886a8d8dce4af0354177d2549f0d17fd7246c59ccdfff5440ffe863ce8dcac39809a94a719790638c5ca12130ddaf97a0211c06ca04dba8b65d36bc1fee5b922eef3a732b2a08941f793fc41ca4565e7a6663a8a2c055df5f54160011a2e237e3f8163fec107f138df2dcd8cccf2c076322322a6cf06e75a1a03199b482e089e0b10372c0a116763f87f8184d9cd9b046cad97b7e0cc86190c45532c6cfe0eed2a86f6e3e16accc3e72aa5d55fbaaa3ca5efaa25ce09fc23ccfff064dda6a96eb0cc2d2dbadf35dfee5b6d5455bf50bd3699f02677a420842b6ccd02ec2b7a776ab585d67637c507a8f22c4498a47c2547c76c8b216c6f2608f334c5d422aa8dd707a988b8a9c6f1dc0b42147aa514374803ab50897500797e0d0bb739ae40b7f5b8089427d5aa6f6c6efedea798cecb3f12accec4c29f94f328b42ffc8f241c53f16c0f4686ac0dca22e527c93d030e5fb2ace41656a38308a9cc18bc60192f75db8c213841ee852d1ad2271df16f39140ce72f53607d95bbd17986bf1a5ef2c7971b30a26837a3f96a5f4334d4d2a6b1ef3d067854802fab3c8aa8d4bdd92aa92cdd8201ab3ee92e441e46105f429940fbafdec0ae486f4549f463feeb1ddc83eeb07c8360f35b648d9903719afefc5243cfa03b1e05662c3f8839c529de402eb7739ff1c4ed91930c35ff4fc5135a3f745d667734a5489e6a1840f6a13499d3951c390319d865b213807a2d444260d46949e3f6e14b3d996777d35cf035b5a97379bfe1c8f08f40a9ab0ae5a5e7800c321fc638459af0f943a98e437c0f880ca98bd9d53a9454fa24e3008c11a02ddb8d15b7d540cafc4b9a2de436abec2faa2072f4aeab5b74bdaad00cbf99fc9140c2d20571036e72cb79a15b036dc2df3068828a86bbe5b81d0e0aae09df41ce5180b5708b7082614bb41ec129a7fbc5f98b05f356375cce335d09945dd9284aa3128cbbe604d58396d754b6d6ca903518c485b3179f2f0f973866ef047b811ed3cb2285a2eafd764163ad6e148410cc3501b5ef8120afcc45a868f6140d32e1d39b3b402ee2ab38b6529f0d8379a9ca3c8bc63485c5b243d15da032a2e68b3f3ab0f31cd4602c5bd9f1e312c67246501bc43d41d8a232604d30dc72959d8053ca869ddf9c2236ce71c25a9797b617fb45236b620bf21f53cbc242bf07dbdf53e45798fcacc372a344224e4c52eb908f274cbb82df615e1f2dbd069d56f703703c956389007f074cb506bcb5d1fc464098983918a5083048eff0dad1e4225f05fd58e071ec7a8b97060baaffab713f8c208f9ef8ac452b7a60e7ba8ee0c4598da9d8c3fc469c72cc89333519d5132741583a7341ae818e9033de59700fbc886f82a39075af1b23b34b77d3e61884a5616f5b4db80cce5a06f70e3086ecf79e0297a898dfd9687a2ddd00157ef2a6d4dc6e87393267a51f673d57f1b9bbb21a791be040f3621d0d4dffb2ec30fc901c861a89aa26960396bf3c9c580fd24a81fe9f3dfd53bae065f77cf55ab5efae62250178185174aeb86b562a05b08cafb4e26899179ccc498223523f98a138cb760c530806192a58acc679aca6355dca5b29a504eb8162675a2895dab7ffe088783b1ad0d981ffafb53769d58959e153124e4e010619d690080793e606500f30e1172b25c58c93f94a98ce1a2c2c27b6691e62bdd19c483c7d542055803c7345f3ef84a34a1946a49710396621e2fa88b8d3410de6b13fe438680d4f404d4b9c6d8222d0cc53c8242b2d0dd099c9958b9bd754a6d2904468391b548e39db27ca95733e3dbc67e18e8a1a90fb73370f9050228bd2ac62073492b6c7f13758e1514ed8d833a394f09fe5f7a82a3e0e86f4b921699cc6557e4324713f522fcb7b72820da644fb086a1cd1ddc6cd2d79ebfe853dcd76b808e369153886700fb81dc9c815fe4dc8755335fd7ce8a33c371e58fe895293a8ac6b7a225eb206182ff5b7b00a86a817f0a0cc8504b3f224f4f1af76e7190f4ae63dcb51589cd3045ff117bc349f6bb2f27202a24f418c787c738378cab79604f6cec52d6bd2d210a32c87ac39aa6a95bb078e7ac02993fb8598d9647b75a2d0c80847927b0954aa321b67a6840c42547c46c5486eec74dc0ae7a1008e691824da496331b3555b44bbf8180d5cc8353d98db7185ed856192214a199716791e29c041f5a860591353eb4ccf3191d7da3c5768adbda27cec2facbe00867515cc347139d8282d98df41ad151dd12bc5ffc9a8e3312ab691159014df40e2913fc71824debdd39680fd491cc463edde8f9765af06e3d5c88c8e496db4de5be92b16560bda32e922fdf0862c6f35f0eed4d6e424de6435c24368b3de9fb14fd9d93369f043904ecabd7f7ada41a173298b334f03780ed4a539702c43d864e4cd137586674d70bf3754a0ebfb12ed235e8798d5be1dba67abd162f0992e3d92d072f3138f966ffab13bb11b35ec4ee8fa03cb8a68831378f9348a325eb7e5535eb696c4ab603e4364f9ec1fe91383e4acb2dbce7f1a91217afbd4f8fea5cfbb056900a4bf4b0fc1af86e15881313fbbc3a6b9dd340640acffffc8b995b8312abc68461334b4c4e03895275e1020a99f5f44e0e6e1a5ab9b23a526f3415eb6ead31148887fa3a0bb2436563eb80d53c130c93571d3186e6a28865913d2084bbf32b5b24756a99ffe0353f330bc96c03e2ea8dd389d7c872e43315f5e48edae1914c8d63232702f076f768ca96f86c1c44aab7260e0311e8156fd450744146668bba94ffdd1dffe9e21548382acd1f0b491d57649d6db359b3508473c396c878073ccb369e2cc111695e166d0970a8ab2ea2df6e9230b466f944c50aa255916d167fbb781a07e4d4c2f43870be4ee3086ccbbc0af278fb6194bdc0edd93726ccb860ff47ce55c400a7b7b012b8246663fae553975ab03a23ee7fd46765d78e07fa327be58c90bdb9fdda67eb61fd1d06f8f0544a055f29746740bb1ad17018bae46e2c2ccf35d61d2bfed401cbb48b17ba8b27ac8b0361d296389142e7fbd88658175ebc59e2307556534462c6e0e4a88256b87e3911191006cfcf92a7a131e250d54875b33028be2d5decb93a6081783e13a733acd1238f0241949bb8a53a8088020f043baa9c8e4e06f2ead7f0344bc234718fbb54952e049b1a1e50f4be20ae2cfc8a2de24c032d06b83b6b7082e2330c326537f5d413baa8d5f1d07f3d4cd91a7c147293406a953f784f15378b7ffaf84411008a71dbc6ee4bf68b5c59b82ab262887929dd75177d379887e13aa1640738b563f45cc615ad8b7119ebd0a51674ec30d051d7886e4150e25215ef875001eb607fb1a0acbb2e45a17a2fe06d038f9b82b39a20fe207c8e4668eb0f7b313ed3583ab9a8a9ce7c79b870545c2cb6cb983b5fd898e9eb76a8f37e546a86a3af9dbad2a12116eff03186471cdcbd92a8759ead6dc28d3eb0608995a392bee2c23a579dbd3e3dc989fd68e19dd22bf9323e8e0c5356fa4abb36f5cdcdcda817072a700232cf06d2631dd190de468b437a4b0e809ee5826152964e7127fced26402a70a4bcdde4d62baea1094d84748c55b2d61d196727d43e9b146559aeac8824d1ddc2462e4e55b40b6f4e510f0525b6fd422e4caf98552948b3457b4e25315753148be7537db624b92e62a1c47468876a5073df552e0a164ff28638988a2a73e16cd441da313e4a110dc40d161de94c2b5d610dfe3d435723d368b174cdcdc51664a84ab9fa2f78361ce462b53f4dc9053035db1bebb651afc525df9fc3a15c8b204f09cab4139abbf815894400445ea0a367c21d803d41406f98d4cc941098a2b8180e758fe91fec4a81caee98b814cf1f93a7ec9d434d0824adaf7dc7b1fa283934915f9e748785e79afb239d5b88ca8f3ba45fde17c15212c66a134a3b5349cf923d3faeb1d0534a299927de09a948cf9f4288ea0085bb771525eff6d1483afab6d3406639ba40f016104c2da820836c3da603fb75575dd4d8e2569a27903bbd65fa6adf71e345eac8e219153d3158171ffe9dddf69ab1eb6e33155e835ec415ef44f1cf8a67d0ab8e5c4b05e793a6ce83182898f889cac9c626e88a08edc04358b79c84fdbd7e7ee08e3597d6210ae10827a9e7624ae32cbf8907701fca84eaa4f9c45956389050e412e161571b39e19bf0e6f7a9c152c76c7009ce3809ea1189a6de811a801532acce83944a1ec64e4b12f29f874e4bfaf855d72b023bf5cedcaa1f30584248e33f1a736e0f4d4d593ff69af6e61e581a5d6ba9d7135678de23df7300c54370a713aa6c3c90c9667dbaed8991765445eb5fb127fb231387983059124df6be66585dd7a8187dcf1b3ed1d3ffc4d1f4a245f25d4ade9de17a323a4a2b4e9b9037b649f99fe794e322df89acbdb73852791bfe9eb453a7b5fb8fcfcad2bc5ff1eae14616479e718b1b9a982c2b206a6ba3f126554153908cd1105aafedf03775b11ae94e16a989e667c0ddd125b92cc40f66dc14c3a5751e8cea63677c4f6b896e041274f89c4c8578b73bb100dd0efee1ac96e50df09c5445a4024d0f8b2ea3ac9f0a74b1327bf79e920a14777f38ce74f477d96cf9be5e705156062518d0024b2559c890ef18c6691e725f7ce2fa65f2845a40f03c14355453b24aac5242d9c24b4a9780dc96b3220b8d8910d230a54a4ba896f631ac175f800c92edd3deca7e8072f78d88fae76d6d199afd277f852cfdf01e8a6856a25d87274e4f2cb20d28523a8e06b76232c6b2db0468ad9210bc5189aaec70c792df4cd0acac70bc0f012f4802520af0f8e3ac93f0678fcd32799b2b054b2c66d9750c05528c6ec7922cd0948dccf0a29500f991992964edfa5778a6ec854d5e9c7a98bea4a49c17390b3d1eea8b2c96950698374e02cff0572b06170eab72b92d639c9f0b3a4c67ce695077f317f4cdc3a2b47e484d8fcf662ae51dd0ea01f08bfb26720ed95fbef5af8a47473a8817bd6c5e4741dc4099266d045e67893b1a39aea7d80166efc18a67cec1c498ed55950b1486ca80bcb0c8cdc2f1f696efb334ce9f5e7654179af727b6a9854048184401788d6397a75aca5ad969631d5c0afa9c3f696499db0d8ee5b138416962190614ece66f68125369a0e118c3e393b5173a252fb38ce99cc39fef0513945f39d44f95817dcaef63a45d8db742d251758048de53471afc36aa5092295679f71d55b681f7856f761e99238c9d9adf480ec61bb5a06d16739c3ab536e0d25418a9b4bc72a082cb5f146a67d022c72aa9853900b276c2545fa7d8c61dab2f7e5cf3948995cc11850bc44f75fbca3d96acac79b02c6cdae9947ebf9bc7921be48bff03cd519d5255fed8527408ee2236538a7db3f4f73f0834acf28a61d3b603f9c1e6dfc141440967f72bf884e4d6f778dc16c08d3c20bc69981c944830716cab00769998d432c97f01e80c7ca0123aed8f2fbb41e72f6fc8cc95db5b70b5a8c95f7952b5a1cc1467272a6697107727f606b9e5b6ee09934414d77aecbf1381da4b5416faa0dfa3ef22cdf26c9521af67f3f09b82a07f69be71f3e6f1acd4adde13e96bb8ff78f5f6102e56c2e7dcefd2bd9e55bd3e74325a7fcad73ff4918aadcb4170bbf0b8582ebd1f45471274f0de2d2c6d329d4bcdbdeee8e7e70ec643b1f10604f250ab6058ffa38bee02e3867119fe79e421a9da49a670b9d94cc772df1f2d3a5b62185af5fde0aebc2bff380bcb5e98a5dba87ba297c059c93cd2848ef04834a9037d00e04b4fe0458692178d4cc536fc996476a8ef2a4cef7b8bc46fd78caf9ae6f3cacba78224b328fdc9c170c0d9f82a3669484ed9e8190576c53b862bafc8ba50dec5bc5faf3f9dbb528dfe23a0b0b071ca8cfc53f263dbd998eda7f0a823219133106e7a984181762018fb3fbb9a66811ba2a0aa26ccc269fe7842ed3db80fc48216e21b0cbf7d36f9c272feba778a64c58146c513b0095114366ff868ca3369dcc3fec70804f15556a19ea47ca6431d2f8819d0c85ad1a49d2497ec7bf8a9ab85975f4ded3b9af4983f9e158d2e3e4288388457cbbdf26517ab9339af6aa0422bef9bdcc4e89a9899518b5517757b3994efdba1de4528f2ba826f6940c8956615442ee3653896fa98d1ff951c2961dcb5a19c4aa291f40aee265b70cc8e459afb330db056a7e39117af6edc5f983f43eaba27d8cd69f6edaf7a96b1848506bdc981533647abe8eb45b4fb43dc62bdebbfa3dc3dd8e620e7fe4a17df98f701cafc7cd486d2e168b67a5b650b307afacd25baec5cc6af60873a0fa55e001897505f66ff2e3950187cd19bd3bad3816cf99f73b90534f1d5a488e3adc861ca649bd9a385f3c57c6afc1e8d52b060ef4c1c5687a151d20aafa1d806f45e711227dd7e9687d5a439b64f8390f4f58c1edede5d4d547abf964bfe38976ce92c99273705dfc3c9a53a87d22242a27aaf004d0794823b1ecfdaa925c7038378e9afc2e42066be192bcbdd2474f2d320ee82d35dd64efc43724d111dbad9fc529a5edde088fe7d22877ba44e13fb00483910e7b68c0cd285da7e9e33466262c1b0d57b699222480973f6fbf2a0730e4a8290ea172aac85fa2d3f7d8e9d73766ff0b5632beff5d58525163ae818738abc87fafbeaa335026a90f95c1d8ff9508f6b8d78fb8ce186c9d435a5ba73fecf4a4714d2f4b532aada472a35d0567a591daf30e8c001887c1c2ec2d3944781e3b03a60d3a07509003a89aac0486fb855164cc13956aed25cacbec6f8b8950d197a68bd18461e33ee7ff867604263a75fa0ec034245773ea5943c15f101ef5fda46dd903a9e7dc4afcce7004791468570bf055b674d467d2938222a891e12564dc1db1eb76d7be2f0d6da7b568d9c876f21f6c3004e0aa7ff2d068986fccb77ace11405b9e57c5aec85f49308af4cab287e1badd6f08e7813159fc213149fee2e75e620f7f985efeee80d73576595670492f63dc95e4357faead8c62cab6dd57b380f93a34f7b1b4f6e9e94ef1ee5116ad7ce2d53c171fa037f297f28e47786810de6e44ec28e7940a652e2addd38381b9b026287e650cf66115d93367cb3d67bd486b98985f5060fa24aca440a0e68c8cc3f08b271096a44298042b284de70d0b0c3c03199be85ef2210c446c3b82b07b6bd90e0611e16995a4d5ec36ae9f34972d57003506452f8edd6f362491f11df84051c6595066362c16d77680caab6615a55866b21147e4ff358a1416b9208587dddd5cb64e91b137c432f81534874dfd48e0ab5115eee98180d3be69a3806c5267118f9c1a0c4580b4812758f58166c460a9598bc06729982b8dc93d16ac2a8f6df5f750e321b10bd52dfd3d6e9de28558258734e88851ab065edc05b0d33ee1dc731b558571718479dc8f0a83ec137ae9b1b26bb40a54e113efdc1945c584192589498c70ad8d123881a7ac8e56324c726817c040e59ab4c088b954a13333baa2fbf2dacaef5534f8adf109f259e9ba83fd449234ad18d347afd53cb997edc27c6d3e8dbba01c720decb1874ce2ef201d0d21dc25124c6985e4632b373793cf83c2054b373f8d8b94fd438cc0c554db2d6345065c637604b4d4a7c9d5841754cf5cfe0b06c0026fc5532b98f18de2bae96b4c29adb3111ee5327d79faf16856bdfbf6d990816ccaa81911c32315347d7cf5bf6107c26f8c1d0b1449b00c97cc5b9d94d03749c515410dfc326954d49203f2265e0e8ac2ae593ebdbe6e013111e1880b592a62a9bb222688e1ab99aca798644e1814235937e26a40c0e24ef2cb99944f9b3f092d5d9005da8676136d6f7ba74cb2508c455d5de3187acd047f51bd5df9071fab5f44f27c3793cd96c26d8aeead6eaee8b446c2696390517a2b3f4a8be1d7161352c7c623a2456be8f2a58a01a3dfc39d705e603b8673adef8f800818171f84fb7ccdff0d85183f57f71492a094d3c1d64c5529ef8a41748a938c3ce83fc6fab72432deaccb1c18b1dbab5a61a63f8695b4258332d55cf14065080fcb71e2d1059c72ad5e4c987fd346dcbfb7e2816e63aca4676af47c8d02a83c30472cf009d637bb0150fdaeb0e86d648cf12452a2e838cba02e372c61b59640f667febfca9c4536695462a9905f1e010b71957e96090fa86258e983417364d3430114c832be0f5e4d264861fb0f161eff388aa3e4bddba0e630c07bbd2aecde772d2e502ca195510171865bc4e2495d978af0b04488982674f259caf52ee795cea15bd9d4dbe9d1710eb29f904710ea06dd251454af2aa8dc9ab44854a088a215202564d7125430dadbef4e5bc2f7204846f8077c885855f9451edaa5a081e01024bf33888629abd983c842dcd1be2a894549c4b306d9be4d87e8fafd17f2ba9c82e1ac098343c92fc4357e0da092ebf49d3eb1e7e3372af707781ca3ab1c55ce0456e11ad4737b4edc6fbadb074a6701b666d4f9de296e1bf4d3aa5026cf2a1be246bb9a5984803787af924bff2aa62c4048dfbab03c3e07dfe0e76d19b501c0cc219c3eb734f198715d26af46e024a4f57eb0b91dd4cbcee6820f33f2f639a9353d17cd61dee50eac67ae7443709924e7b176806f93818d7f2749eb3888687a8ffa2fab61788bcca348c6f04dd57cddcc41e785a544577d659cc92ea2a926b5237adef535979fcf3b99400a1a52bbff935b0cacc4135943542c2b30fe2ef27d191d43912615447868a6cda822666d3401363ca94bf3df913fbdd14d89381a1e6e9517e8df686f775f369a1df62ffe894c65beb1236afc15c6a7ef8a75594e98b4faf62f4dea5d740b70bd1e841f6e4f6b6f36f47d0f9a2af3e2c20c32b401da98f29ed68a711854a949c1ce2a017eaf55caad718f28232b17ca0c57d1027ae88c3e1908b6fd34e9b23719df1b769f368e899b4823b60459b00c32c2bacfb52f72e370fcf2aea2d335e4605d8dbbd727a2d96727a1a273255ab18cd7bf71e4c02fca9bce318f4d9295d7fb94072658fcf61bf743cd6cfb4354a9e7afc8605a2debfc67f692d5a768ec509463eee3fc4749a2818c2be44c80a3c141a63dc9ba522e734fa9f349e8a03bcdee22ef819419e5d7b20496696dfe7cf4eaf65557740725fa3a05f46861a943509bbe27093a0df5de0f602e44f3d24498360c7b114962fef7e67887c79343a22ff7895e6af70d4b523b5926b2b25b788a1703b00810722d167617afaed1122a3ce1657c41f5a2455da73fed2b521194c334c950b658055775efb41e69b097cbd8529df3a12aed2bcc4d8516b357262bb24dd9bff6ffb8be6cd6d54e94f3697cd7586019a1b9d79d20dfca45af2d4d4073776e26ee89106c6671110f3ea3fb6a16fb6c3248b241dc8ecda826d0d6d51648997bb02bf9cb380493d8334776e339049957344f1fc39b7bd5d0472fadf414dcc2162271b9d7069b1f0bea24b6153ee6d4fb002e2fbf3ffef78cecb0884db94ff7b7942b42bdf9f07552dd5404dd84ebedd37d836fcd094fe7d162f3cd9d25a302a0677a7e6fa8b5192b0a2616766011cb10825e1f31738a100540933334f99c5a23e2e8082bd535dbc6a06d858a98ac5891df6f9ab97e1f8173edc2af56a5618cb5102b74b66efa4ee6209a862bb8207b2bf9324d07904cf0f877ec0e6342cde3361a14a1811b63de98e9f718e204594c2ce62a5cd60d9640e684dda168c6c766a77c1b1e3fe94df074b3e8dbbf0672fa0c3622fcce45b3432cabdffd748dd2afccc7846b43b7570a5b05889aba0de7828b323e12774c2bf43ff9756fdf773a8c6f13b1c8d0750b9f66c46eb9cb9027377575d8133a0d9bc870f3927da7ee90e14a0a02d5e7f914d316923cb1051e464b6550bbd54af83a16dfc2f00119000b51cca542d2682dd97fbea4a1b069ffbc517354059efe91c9a1bab18358365391d686c3de7881c24e2c8e88a1dd95c6e01f795853fc974f0eeb36412b33084df547e540269486257fc8c8d98665855fc331cdd3fb2ab3b6b8a5609941185a977b83cf899fb4a457eae630ab951e492c44b34f72b63a453c40f7a408ed6df97434081d621d4a1b7104fbceb003375974e91d3b4e3e4efae69ad1780784143682d52e110b0bcf13d85d4f06ae18d99bf81144a68c577fa505c897811555a7313ee0e099cf6fd054183869c1b8ab73d3d03a66b850af35005d9fc6921022562b4d9bff5f0ccb52121679f2a423ef0dc32ea33cf96f007eda2d6a47c423f4ba0ed491e117d8f750abcdea05aaaf88e7f5595f3ac53af44a44c7251266dcd5c9d7b864e06fabbbf0985662c9d688468f1264d954b3b0f09ab646e2f463c1ccbf6dfdd9f4d234a110c3f0dc21c4bade062cb32a0a546aaabc0e0ac52e965eae02fa7c32d1c56c6c46853450d7437c23af3d9ead3ded5d658315bd81561c6d5a467a5c0ac47dc25fc28568f12e63e0099d3302832f67ddef9ca681bbe9da6042635560a8299c5158acac7c066f880b51848570de40fe32201f396729f5e51922ecdfbec4798ba1a8f10033bb9bd7742fb6c14f5f552ef6b2cb0b338bfad21cf800328b113068fb2f29891a3463d73dc1b2c977b0c65645a5b787df491222346c2fb24e9991333dbd690fa1ddbb6637eeac865188a66901dab51e06612ccce1cf3dbee2fc00940ad675191232473f6de9c40f8aee056ab2cdf7fa9625d074f0f0697e9d2d0b5c849cedf20be2c2f84d7af927812886d2bd8b4d5ceec2c52a7d96e4b18850f47121ebaa51ab1f32002e6e16406ff3d084c04abaed54d6f7c0299e85c09b88714006d6a33016ce163c9d43e3c7d6284311797091d6b325e4ca23fb0d84601f99cf120940e321efc44ffd73c09046fb09fda3395de49901362ed28a11768e074f9c7cb60fa4e3425206bdeac444706d7fd531b905c267e4db97719d16c139dce574b293a921a6c9b3f98041b6836ee99116af669591615d04c54ebe4040810620278b3fe1ac0ea29c64e6ae79b05f5cc7b40d343f56a8268dbc1b257217943588eec2a2cb15e90ddd381372567a1c5df6deec950336fda089698cebb4f2f82ce65f29682feb9d42b1aa6cbaa7e79a2df1438d263ef5bb8c01f836404d934b29ac9db6d1e6c27183ab33141b128a39834926606f8b9f841b74990af90994d08a607dcfab14b0ad03cd4ff084f82c708aff1c929e3227e4bc5df6e53531dc4c8c1f0754827f0c25700e7c7ce9041e940bb2d9110c55fb0b4dc4eb101e54b5bca517b3e1cc081e4cdcec849fa3dd09910061921810752c44fc6f11a0cf486f4f02d892d63bee0f0498f17677b83a034bca4c9194ec835ee0dc44bb979cbd4c8160cd526d29a7528049ef0f01250622fe224265f4cd27c8432cd90ccfce39123ccb5be7a4ef27178a1eaaf0d058dd3049549523355bc4f2066a0cd16236ee0071504294aeadb575688356624c5f56c1785b6d563aa4998515aacb23a1b9139c6a6511ed7bdfe412fba2df1f690423ed9309f5fc23fa9f2e6f0c57ad95f39d43dfba9d54e93675614090fd240d538066af8cc148913cdd1a7f7c613c51ed95db5aae103b223159a03a5a2851eacf3e8c571ec3089a344ec5241ad215d5bcf2ca7a960948b1bb195edb90fc70b2ad4b7c22af06b6f93984237b3ce688688788e0e51ff1ba475de0e47013737fbca8cb6bc5e00f3a3489e699eca4fb9f350c337c8dfca585d461ba33d888333d94c674048bef4df5626d21226a6b4f2f6f23a15e49acc2e83f4f926beb72a2e72621333e9f973f5b8e69be79223081fda5a88b6dfee5c89b0d2ef4cc5af12148f86ca0f8db476d6524ed6ad0f0f2d0631bb8683d93be772a1b8b4a76b05b62b2a948c92b851271d03ca14b55942d62d76647765ba16186969e13daf4e0963f77cf3c2e620612c150e7bc09a88fb75dd2d4ca0ee81d2e756016477731ceb3e82ff3b67551de5cafdbb8604da16915b214aa780df094828ff6c80353b68f8b236bf16515a10fc34c743dde69f3c832c9e18ca7743f4c8f8a2f3f2228c5d3e18f4b7b197265e4d5243e880024511507431bad1ae1588cf6c33e0c6ec5ba547e0640e2e82b61431fce75f327dbe3205315bb049887b2821bab79d9a4b86c335aaef3d53cd456c50e402c58e9558f4b14a68afdb3a47f28819063c48cc55a2f3d5b31093017b4f224abd77fb179924c174767e32f6e10fe5193b584ef4bfa7e5987b13663953c5dbcea6f13e0453d8a8e4df819f08339c2ca09c0df82731df74dda31157a380116ad8a19a07648136ddd64cd81eaee8e07b2417f39abc355c70b4ff7a017ce805f37bb5c763d52c40d5fe2af7c0e2123e8ced92dddc561b8f0e3164e2bf2ab0faa129681049a19a0acaccdd1a3b56d2523fff223173ec66cc5eab731d87b1eec710a928f5998548336cc697ad214398dac12c187f405a23862af579fc8811a52741653ab4ddc46276f0800b35fa905cdd3c1639e9e74e0e30094703e9a8b77811e29ddfee007252c9f397a5105a3b8626f8821e67232d86428349b9d30078f5587c31813ebd3c0633255977e65cbb0eb1b45b78c0578757f3d364467839b5ac3b412cc78b7dd8b35dde2cb0e3aaedb271e6ecf7fb2a65a18d8a63b63c854d01b506a10aeb927f1d8e8fbc4fdfdbea05dde38122028af53eec187cb4c577a97e1dd30fed4d16c7c671c69e535aec06e0a38198dcb687cf1945fc9f25c0d6845aecd071133b59f5638326eb26151663288c9f23ac7dd8b3c808d771753b2d8d616a76897396c40aaee48e2633a9f3638f5587174a718c9346590b8c43734e692ce3f806687b29a62ea85f483a591c43802b01b7334170ee649f010bde035959901a98ec3285d880f9a7ef8194af3b006f3886851decf91db1b06928357a03261bca09fe0e39f257f2d647441b41725a56cdc85ee466bb037b4b4f3e2dd773eec1bdcd33d9b2502a458994ab4ba7eaa18c8158fcd24c3f2e10e4f1b936517422f75b67e24bf5ab36e701f7e46bfc560b469c9d4e5bcc0d93e3965a23f7c418a07d6dc26910df2a1e483590f6dc88700d52fb74e56edf541b1766c132d32f289abbed5bbba371bd0926c64c100c60683ea6ab8795d0c640a91290b2456cbab84c270d06fb1d83337985812943ae7af55a821c95d1e63141e7ca4dd7a1e297777c5e5a12ffa22912442961fd51c7b1f05ab86f6a7810e2b479ff08a4cbeb1108e1c086fa641097218b8e1185d6fc9a38790f2f852ad242a9204ef5eb1f1fd1ffe47a5b7a6f810f2364ff38d8c2e331b1df0d16f48aa212289d9fc63be20b96fd7c4c7cb1d053c047f57106ec09fe6e7ab886b48cdb2d7c72884b624d8cd109ed330dd0ebf708e9d8cfa5290b65f8879405ad0115e19942a46c321f278cd6f1bbbe134514c296361be860bd82ac9d3b4914125c7d380693ae1ee5d335fe00d9dcec0485007b53642bb017a6dd3d45308294b52241a5e79b97d6e4945aa5452f45e4ec15d49b5873634c92b28c89287f8deded5f312fa737c37178fe7e37a72be76b5cdf854fc724b1f0d332f201deb633a9e956da2c50571e4419455acadf6bd1bb891901fca1ee0673f116393599a68ae8e044271c340c46f5425e9a127b5a7cb73c3ad8b819974c0fe85d7b5bb0e9f5f917392b4da111141bae2d24e90a7448e7950603b28960e7233fbceeea5cc279f66ae3d07812bfa7b34747051e0ad5ab31f8068c674a9c7210506379d0621c1ef86ee227b3c73a84c6d179e51a029aaeb513662c5edb199cdddae428c0397a383eb19c80ee88f9c117ad6c05dcef17506b64c2c4a16d5d7b3b5d88ef8c3ad524c0de6b17162c2bf60f9e35df6b3b72831e19fd93d51c5e15d4417b322b206b6b817b37eeecdf87001202646e6d373da68ae911ab72556656b1561c6b91afb5e8573551924dbd6b4c783e83fa703a3704a6cb4b2a6e52fe0a1ead7d77a96ebda0202a312175c6288d9479b49cb157b68c31eeef95c8bd237a5d7b75cee9de61652c347c57544e560acd54d4a35308fb5d39c4ef5140e89396c31a47da34a7d959361cd7eaf8db2c8eda03e34fc95508d84c52343b731ed6ccea1492da1c6cbd397218c25cbdeebd26756b1acda3509ff93b19326a544d72b34188180af0374842fb39204b45095377499ab204cdd0c421f11bfbc3bd2099fa58bf91e4fe801661a59c3d45dff3ed7da657a7369350f8a1ce10cbebb70da2e79123b4efd321feffac463d7beae90f2802c8c09b2be23f1023b3c399404246bce06445e0704748d0aa98a95c13dd9f06197c7059fd4b14bf023971ed1696d2b0287343af6bf54332f74961ab2bcba651afb907393d47154f6101612dae8124a8971b5295aa9443ff91b5e18217a35828b7bad29f8a006ec0ae39c0c2b56c447b359fe916dea6ccbf3634643918b88a04b8ce2a9270f61e5790e4ed04deaae9fd9b6db655efb8300d0372605bae41e77e6d9ba34be305bbc7a8226e910a9a9761d34de11a47ded1f8e500eca9e00a96035f322861e072e59d9afba20f979c8bfc543bf603ede98e47a2d650a0e2550e89fdc1d577dcb73622f079ba17f951fae7f0fba221fcb01ed4ffc7d19aa9bc0ba0f61b43f25ed63a1a92dee0d23d644c548b275089754f7c157d96b10acdd925919f59567d28be16cb40338812ad28653d8b323de47f85412fcd7053eb974d989153825af72f1a55643b5c67fceb7d6577e19b536062486ea45eceaa0dc6135bd8a742c1591d2cedc2ad6a6170ec8743d6bb76a29786426eb7d9ce59514b2602e7c04beb4e6682c7ebdf1e94e84886035dc338a89212b4b89cfdb52c1c7c583c7ad8ee9db5813993c03c6ce3d0bdbfb5e7e52d6ef8f07bfd54a736e58a64b51a5c0f6aa9677cf8c10af56fcc06bcf9e1e18a94f11c0021454f5daf5557efc658becb9836b7dd8f34aa44f04732371155daa0ffb858fdee09e828a8bd47576deb13e29963f445067ba57c3e8565fa613aa59b6d21092c0e134fd71996e11a4ce7761c048c490c91f1a1e4fc18810d5ff91256e787ec7038b0347f3a0c5d32b12bbe8acdafbffd548664c838f9304a91f18be8aad181e98797eeb2b6ae088fbc0564c297f2d78b439019fd111a7a4c0293b2682013aa99ee236cf6ffd08b67b023c8787b51644d821493a58acf5029f27526846118fcbd58b545a88dfedcc8aa8299fdee17cbd21c843ce40f7694f3f8933d942c2b3d68d1786b13f9cee49c79bffe816226476d275cb417036d1a355894e78199b3a8f0b09700bbd058eb0d9a9d497f6acc3a4cbbb2d13e67f2b999765c9aae3d954b67f242a3b2cca44b1525aaf7cd97698fd2d3a8240e05ade34da6b5b935d57f2cd501c58266ea703a56cade33cc97ad54153ff1559d6eca625cfac3ad509133da75fb34f0cdadf93f9d088f3825085256a733af6f05394bac57e5b45edca7c582384383daed8234d567ae15af32c2c2a66890736d3d02b907c866a6d015512fcaafb7f023ce7498bc7fd2a242c3044823ba7270bb1f1f7d4276891e0766f4a2a6e706d8af82f1d534bd4f8eb213af083c124401354ef85bdd82cf108af732f13b66c8fb5e7bd1f0e3a19c24def6d88d5a066d9e8f0f428c4c28f9b558cdc67d3a3b509cf277754ee3147a07a81c0456550defec45338eafd45cf7d9625d2c0f59cc94762c35554c6e0262f42559802b2081ffe114dd6a2d1e57ed7d76291f33ef71e67bf283594faa5f6f4c18710b45c5eb980b5a41e12c9a54f1fdd2f248534a83463cecc0116cd2e8e4cf1c0b7e2fa50bf064389001ca8e1b2174d1cba13de044538a3babb323494ab020e5ebb33e42391c0679d0f5c18920328b7467ad0756bb413f227d7861fa56255847289a52a2790a7d1fa4bdce16eacaa92634ff56edc95eaadd4ea7c00223a7f694cdc81a6aa3eec0eb832f55eea58f973dd52881a8a9ef1abcdf106f884ee6633d1f0de61b55969f2c7e5e395dbfc9bf80a5bec613fde9536aa7828a2109a1446e4639f6259b79f226e4a39cd5355f606b1db5ab165415b2eb8f990a654f5a21baf1cf31f4393c7404d4bf5ff3cb9a6179232760cf3c32c93614b0b6f96504a07e82d46f7ec7db09692a9b412b567dc3ae2e71a3d4b21a65c09ade71cead05a8e9fd78fcf1d6bc831d176914da4f32ba1a4ba8255037796404629997b61d4780ef723d56fcdfca26d3664ac5aa304ad54ebc33dff306146091a0f8705ca7120e69104471688666995c15ce56367cf5312f6cb236d1239712a0493f0943e1722f61460cd0e93a6bb36850b767fd432655a9ab4b7b5f44e3ca7a1bc6ec6da8d24e01173f43b53c2c4c1eaa73da2edce4c36ff7265027a259e47a697696ed827f5d32156d9b0faa9dbe69a9c5593842f6457ddae916f57ec4c55e0fe81e9c4bd6c1d7f03f3c7407c9bf61e504a84d7fd294fd24d5e0c1339cf2a4bd52cb9b4ad83af04d0ca990b9a191ef2ebc06096dc63e20d5dcaf4b957ecc670eb03fdee6e10a1b644518e44550a523f28ada2acce35d81ba2e4d22a0ac6e8d84d41f3f96119bf08f167293321637c169e5d032d085068333db3dda1462738d203dcae2646265349402c6aaa22045bff18ce1acec957e6c8acfb27353d073657e6467548655ffa0b49098856c8c95e97a22c2875554efb0fabd678889c86bba229b4739f0076f54368fdf71e07bb06d4a562f249b9c2af515d965caa52b7bc37cc1db64949dc489c51b740d9568c4ec9f101bbf52fe85d4ba30f5448b413b2365ae9746f1769d81de68caf7c61b90a3bdd60312013576fb81748b53970534d5d6a75129fc22a29158b850de26882adf88cc1dc1b1c7d1dd9c70a5176785ab5e6085cb2331f2a90e80bc32bac301f60110f9f89ad694687a7b9f5a07b9c1efe772bbb1811d545082534b750bf4bd1cd363db86973e0edf92a9c3e610bd248326fc7031818e79faa124f7df31710a00ab47dd3d10dad2ec145084567cb2865acd9609aa0490aa4993de81912c4fd9bb9a377329e433ee49bc20538f1ca5d97de9b6240fcfa3b547ddd78e9605284df94dbb532b736b2ec27c34660c1c0a522ac6d1dc3f3d876b2d1fda8e99451851dbaf28011248316f4e713bdf2c98371c00d8db1018ba1bd0c4e987a34795641c9306e74ef509f8977dee720f343c88dbd420f47622deacee10182e4407122c173efabfe8135503c0f0486f3543e57cc34afc8c4319305f86e20961e1ea02f1d415b497b5446d3f334c2a03e0d0b4504cfb26c0983c475f4aa0c6775f87b81f7875bc3f68ade6509a9f8f70e9bab09b25df84c4a52c070d9352d5d7b500d8cd966b0051299b0d494b796fd8e6e0cf86285d1cfe567201fee269d70d6fa10d2f256d77f027fbd56e0b49261cab09bea3742cb0dd093dfe56a26d6fd75a6402d587175fc5dbd2718e6ad6226fcfff118faa59fec1b7e61da8d3044359aeaffbbbcac4709c62a970cf95f653712c90d986e8a2cda146291a951276fae98ab2cb99ddcebef74349b196b80da23dd4a81cd3ddb294584039d2844cf3c262f42826d5e855359a6972450e3906b8924f548739b313ed1189ef0d25524aacfa1b533518d8bd599b2f8863d99fc0f058ab68784a1af7fb2cae757fc68dcfa61ed8be5e0d3539973d7bec51b22eb36a5efdf1ceeb506df150671290646b784f0240d637a1475907424192b6858adfb96d3511d99c12751f5ef74ebdffa37c5f77e4ce4aeb2c3b11c9a2b02f68e7d69a4b5e8f98d45b1061f3ae4427cf4fd6760822f9034bc3c01bb192b17de83a36a8950a5e19d2836ed67d70a5ec533177f94f511a33ca3646146364b6fd45f49d98e6623b44cf2711623fae02742fdba49907d7e9c368917b4746b4ad1002fd68ce24ab3a3615892e002183a62e7a024e4ac6b8244bba6af597422261e6b41c6eeacf1e2ec0a954a337ae55745e62fe784a52082b8c54063e870baf1c0a5c1a1c1451c2e61587f64d0b5200a7f35134871f0540f655a4d65bc26ddc8f03a6d5c0f214be26621aee51f8af579be8f1cdd096d657ffbf0a33d75eb295cd524d9b35a39dbf3a7c061dfac8b33f4cc45ad59d84b5b0213fbcc359d403309e0a14738fb322b9c06a6927f177f6ffadc39277fe4e455354251101a23741f954b3acae9a10a71b6809a417a00d82bb785897335109d3a369682213706ec1e3d0fd6518c2f2a6fa1dd9921cd37f7cb89e3e22a7654465305d23898bdfa176037d61d3d2c4f350acc4db0e579505eb736bedec9cdc08075df5e97fb65c2697c20c53ffa53ee31c4b2fb19835b7dc3a9e911769ca750caca304af9d51a1319573fa1c3a65b10bc6358c69191d50791f3209aac92b37acf3de4af8510de57cc7b5e2e3e530acc069a41d3f88d81df16a82191814bf6fa3a9451e61f6ea725f5568898ccd0e26532b383398186b821d50371c8d5f21f4375879bc5b0f6dd7ae41eaa09c3e6848cd0e740c536731ae2e6cd10f457266ed551ab0de4e741e4416bb00af4e11f11e5d88b10a0ef452cd7a09d5a5cfabfaf7fff2f83ed70594701230db0e3fbef0771a1ace97c15d9364d28583bc7537c17c0461384ad65e98a17a33a09789fc7898db044d68da62f48db0cdb8a33332e1b9d243819883606ba49d1ad2765e1dafb4073d77a6619a06030127f5be07667361ce3002a3ce0bdcd978c5a1d9acbcdc3754d6197c94c49b4674cb6a8ba320712f577bd2004224a4d9a2529729c7c3a54770f4840f4fe9470b559eb0e6fe5f80cdf8a388a0231c939933fe819d0e2a2b73509a6c9140d30d936d038acb1601f81e0e61c9578219ce514a02fda5b2c0bb2f223081c30a0f5e1902fa5f7e0c50407edd50deac175bf2581d0ce0abda20ef4429cd5733baae143312faacbc0f1e08bad46cf3b1893f2d9d789a16dcafd7f712759094439f59a58c57f558004c7f7cf70e260d63d5e4f80ad01ce90ee4fcd22726d2ac6076e62c14f8af5c904101854333d825fe546d62ebf5487921712f01065afa58d4d86045ac4f5dcd5a40ccc4688fb89577667cf0ee5715b460ed5ac1e39feb47d8279a5a1ada1a9c45fd1d4db5f8051d2b5753f1b758c9d167681a88924ad2373ee3ecfbca7e1eb6a670b1276b0a5e647a2e8daf597cdf580dd28c7fc55d960c2c6e89bc78921ac48c0dced939d83de9da601e2357e6710148b897513eb05c2c9fbde489b1da4224e1667180c0cf531cb1f2605958590c55fc858b844da8bac0d2c543946a423cd73748a287e774cce58383249c7357a2dade26e89e39793205900d36b2c0a1ce273800e32ecf2ec51517f380a584376b5499ba86c1a98edb9480e921b3da294ade94310ffab0b5d4dd623a2a5fbbd2b77f7dbbb8a39195917342bdb8ba945071c181191f6ba4fcc809b4d58c6109c365e416c31c9c5b041614fa8c97222308ae597c00be0996ae70a1656b972bd85e0fdf31a5ec6bb3411c69e4da14c9075d7532b9b1a727b982ff3acaa18614263015c91a22cdeaa7d072b978ab12b376d90093a7a0dcce15d470fbf572b77983518eb8e934cf014e850795c68e51bfedf6ad516b3c4e60435fc7ebf13488d753733a35d144794d1ce0f75ee59bd0bf53cccf277898dd2e506a344d88c348daa88b37313526817eefef0af5c066dfed3fc3cb030c8af9d11e61889454a6f0ef100ae68fc1d1b814d8fa14a4d641889f2741292f7c424d1549893909c0ebf428e202cad2b13d620774044618e6bc674c9ab92b8efbfe1e06ecd68e0c7ad8f16ad79436c75478d831dad7287bcdfec33546d1ff45e0a482ad6226d0248188b7e93e1b6d38abaa99d13db9600d0768f36c18da236ebb951159c1f9fe0479cdc06591a0549db9101b44cb5826c8507c157e6f76f006709f7a657e808d4b83fedf9f0073475aefd2c6b8506c6fa4e301a742ad55cee598f6284b077445f3c63e63c8fa4ea2b7c84a5b23cb5ea61527fbc9eda44b820758564b953529c21e3430c8cb5024fd0b526b5c83dc7c3c5c4a09d07345ffddf5e50af9e6763c3dc86a855e5036cd822eafbf44db61a812ec76d6904c42511902380e95b91f5cc1e3437b2a985a7b0205df192d57b5c852d020046902db87551bc58d6817c7617a364266470c7cab4855ae39303a5642c8fe25b361930cf88997928579357906f44e25b2eeb2760497ec3f4ed11b8238eacc53f9f2006e030d5320a405f283bf571adfc6ad3ea969af7ee6ad5046da5997e6d654b83ab7087ebf1d8e9d74ab147cfc0b0ae4fc5230258067b25922a14a2c4d0485100f64e1c518f1707067f46ee14f221f2891c7c1f27cc0f7aade6291a1f20ab24383b1b0673e91e1700ac0d46fe8be5fa8b939228b99d2569f9a12e97404eb170bb1d7363250f0b20f2ca6d1866af59f7862ba5a58c8fe90f00678cb6cb2df88518bcbae371fda2914369ba40e5671b1a2893126db1f769156ffa4600edda9c9cd5336d3d09d338ce1bba82421e03e92060277c970963955642ba82b7a975490455332d4a7dfec8d2ab303efaa986e348881cb83c1c6e6fc3834af1ae35cb1fa8b6ae8dce245cec5684040218876ce522b2bb5ac114e11f9b92445cb47c985b2ebc12cf12def3f416a8daa60c06770d53f66888d910ad454b15f00d451e0df5a80330ed92d7ea162293fed0d16d115e42220b7cc69d34e07144d98bbf6ecdfa6e14a2caee4737635f179ec6c3c5949423a28c291e325452640300bef4bdb0e78187bd7422fcc5cdfc3455b06afbd1c099b068c368c6ae44c326eb0e05bb61edc033ae9556641a21da6157deb43e114a64db0b1e63db1f6eeb2b8d7be046197eacf855bb097c8dfd04d31eb781ef6918b07a33fd3ccdff3bc7f97d2022e60b30d95d369e4b3448e255d4cd10d47c78d8f8a118d47886601ed874779602b070075fef95ece6610943941d919db06533ee30ec10704562ff12a9ff963d4f0ad458b5d54a21f92f6e7c58ef4fc4cdc1ec703908ef27f60afe4f47ca3f86cc64c6bf11b403dd2c48a93f2c8992e0b1e2befc602bd41089ce34be8b35af7193bb8ab0ab08d8df30ac83be8034a2cc419bff950cbc785232e35ad24d2543dc5dd566cf05b535315e251d236d1fa86ccacaf7c3f4b1cc88b699f580138201983af72bd16f62c3e7dc2eb2c94a322a0c95c5c15b98a82b707ede1456c635a7611087d0b274a4d316d8e88379a5ada719242b67f10ec46760b1be6d8583d9c1c11e12d6ed730bbe509cda6ca4568d2723a3f2b0f384e88b8271ad0811fcb08b34e8e07ff358db279e07ffff515da69ba42d0bb32a6dddac8b16a606b435afdb870d9626cd916481026a0784069c4cce4663ab0e2501d6378fd951366884b029a8aa7444de64a30598e779dbea37599c9254a9b0f98c9d9ed0603aaaae59f1dc4f9955e197bfbb26244929d0f58552c9a95684840d8ffffa89869f47c451b78eb98c83737d1f6b8c845e340477d5d168e7cc5aa5967f58a0b89c8bf6bd60f30305e1e7113cf9acc8551e6035f3516ed7cfdd126788cb0f92e8546a4f22694aebdabf7f62fb39e69eb680fc9b7f452825a50564fd5e1583c979bc4b80043239586a5933c4a4105170dd0ec446c0ad946ebf7bb705379f035595621e2470143abca77cea0ee8a863cb5bb89e0d95a1d49b5fff6ee0a9d4a5a3821561b81554f6d851c4c30d371c570967e10ef8db04d71bdb59f876616be054e19a37cec52f63ee7f9c34c80b8ff5250e7f9e6544857d1d9bf5c7d7653cd10fa2d72738bd41f36b0cee1407e30cd973c62fc1a9978b44fa30314a08010940a8b4114c7d7933c4ec121b3235a4552a405ba54cd0f5c6f7e813518def2652800d8c61efb34df10c6498110cb210f53d885f7d931f185910aabdb7412606ce480e446f6eca243b471b9b04490b7d811a5d0fefaf0e00f7628962a7c05e46cf944df7157a17700647ebf76de699a0f13f006a7a6de080d038a27a79d02f72436e1fd7e2c7366cf98611dee22b202e3672f7ac13b5e54b3991a9c79fddb65e62448b95159ebb213e7328ed38e386d2cfa14ffcaf8acc2ec768bc13ca26b28e274dd70cdebc4341875fa61893a0ed4d8b5eced0cb9d3619769c4311749e5abbc82e347c57a2014cd3d62b662486033feb77fba62ebbe2c70735da9240d39cfd5464b8928840cc6899b102892cae2df6e4e8607f8131a93d9ed144815d1abdc155bf56aa8fef5591fdef0a20f23b4e4267f58e7043be0205d141b2682366b428793a0aa341da10efa718b9af20a25f15eff2180caf4982d1dae616e821664e4fa0f22b7ea10001eaf067109b025ddb2b3fb0d4b8ada93b223988ca08013be223d9f4219d3f206220717e4013e12f7541dacfad1609727b207f8e1c2b1f78832a79a3ee6f757cd11e8cda86f933caa4a317a6165dc62263ff15d77127f299a22d648a6c4a4a275010e673773d42ce0094034b034a816b0efbed0dc27de506f5c01f3cc96296c315a90d0eca2f241491e010b02ba01fae7920dbdd15bdd93a94e7860505945aeb4151b93ce94ec0ae8d705535654d5e1096a4eaf19454d0a4e1a8005b0101408ab0edbc9c286ef92caba1046d9656905fc5a33f6c9ea6b69f7d22f34b6fc70cf13240c4a96746f76c08739149bd22a81e96cca944ace271b8b529d1e938bb7710a5aa049a6f597f918551e7028bb1d91bc0aacabb74360dd11facb16fc3576bbd14287432dce4804d4362c0be3c205ad63c03730c59b2d16e5b7509c0735011c186eb5fe6b9179b61ee3de48dd1a6322ee7764327e2b91c944442ca2543e55630cdc906282ef82507397b21904cc585ad4cb2bcddceea1aeb7be60c5a71f56ad0871e06576474a97d5f476c40eb791aafc926c0b0dddaf802f7a78d870b9db92571a70ee7583f4073da17838d9b6d92cd83713cb68af7f00cb08e8385d91c50bcb260eb56a1a8ae82b0a570682a16c8ad9fb2c0eb5ea0827b7524dde5cc55dbd906ff0a19a72db1f3f8d6d8f3c0c6cc28dbb98937ddbb53bd814a2a6d9de3c30b82103a9465297b32539c5acf5dbf7a90d2773894f256b53cdfb521a4b4386ab3bc2b761e9a594cd07a36df9d102fde3164939fdf424585e58480ad4a34ec134bd544fdcf94ca1384667936e65604ef43551c0d0309a183dcb7be5fcb9f79c99d798116e5ed45ab80e187684968f01c7770e3d63e72ede79acedc27b64cc5c9d3ee6a7c5f8908157df124e6e7528d9aa9e15f2af52250805baa69aa3718b34ea61f7549e24bb16ff99303df5a03179294bb0e8e82d42903c7cd69bd88cef7506712434c12bdb3d0ebb266aa7b895804b1f8391e6b0dc989fc808dee2b211ff2de182d317658ae70c69cd1a2ad30066dcb100351f92d7979c3b54a6945c7754a54152ddc770c503fdb525739b7043be842766372efc84a4ca312670e624e1a3717f9242c193988969e8363d25e3e64605a18c8a7d1a39279a117165dd1f20ca317bc89781b96820d9ed796766990885bd98ce56ec16cf91dfb1d11b0022cc2bdf6b8df7465546343858695393600a94d35ac12dc35f5c9c263b1208e0ca857a7a8fd45cb35e0349e0b2251d4730eafb43fdcb96aa05ae28413beb5e632ebf86c1219fbd0f2850357240c5d094caaa1c5c99de847e3c37bd89b82a53f83ffa6266155b4a91dd42691623056d9c0b55135969c0bc05179ac9647b3bd6a9bd9f8fa8286edd7b0adf2c937b80a969c534bf7c7b7273c3b0b33dcee7498258a23599274eca7fea807d2fd3b5c2849dbf8b762b8b4b33feb06aa4b78e1c4e5d5ab498cb201a8bab5224791312c270fadc4820fe2cc675ae4b5fe6012884b4e904b7f549a6002f0c172e766eb5dcae18379fca3cf5bdc32771189109314a3b0ee8e9ae2d22f65ae7b7a9ce8cfe8221eb8fa3d6aa96342aadf95fa511422b02adc9e98f2905dd26010159dac86af1969ac00b9f6855bb84f22b19cb326430436e0a7cf1562ce4a2a55ab07a4c57c4759af3e78fa0857c073fb7fab5f92a8f8214ed85492491fc41e9895ebfe42149ea21f882cde5bbf9f5eab3c3aff7ee149916c9cb9b69baaf3d81f2fae866544d449c58ce174b21304965f21d8c28e86e19091655592b1a10b4857bd8a447bcf530a4388ef4c82c1f490a6a12e5abaa548d75cd368e1758aa740e291ab166bb486dbfdcc466ad81468524837ee3ad28955cc26e2bcf8eca247be8b8961cab98d7d850971190c4e76565748350531a9d557f7c6721adf33b57f944c57c75216d2696a3e303cab65096197c2823c7132f1177298dcf7686b682a772deb13ad6b5aa7e7147dd4d8d330cbc6a0abdf209b4408f8b172276908a72c92b2bb7b820df685a586ad2431f328d63e0466997fc98c5e938021227097273fb0edec080295f0a6546f1d8df9a86505db12e91896cf5f2205ee911112bcf0d47607cda9c0b4b2f87586ac468554d09a3fbdec3672aa91c6cc25de7ba8750a547ec9f175bf272c46d505b668648ef76834d1ac97f8d4e3aa6b197cf6899c92dc4a5d356967dbe41189e99b6e160093db61e51538cabd662483f985fc3af2354e4c5a7af830080333d0b1db2e71505882505c6548e087f33a2481850fde63529971e91c721edf432b97a50f43474bfe6280291cf002660d7cdc827e3441f7648d069bf7a415e2eba1254066441e13b4945692bdc7f75fd0eaed48137127949ab503e32775fc51b10ebd351b700fbd04bea26eeeb61f6b20e0da14300ae742417cc9e22aad8b49f0df90a3731186d118f39539c7b9f2fb4f809d6b9cd2fd5cd92ba045329202e44506e9cd2eed9e3a5324686da4af8ef16327603f55cd9ebf18bbd37467076df6289d07d407ff68e90ce368f0a906eaf7f73efc3f88c765b15cec37915bec5018a35d2feedc68ac4b15c10db9ee4770a36b8aa3c7ddceada4e6deb8729d9b6f67dd928ae48f0d4e924d86da9607cbe32c30a5436f3e9b667e2b609c27c3b4a6ecaa1999b983a202f828eaedf0f65e2d20ee5c43b7bf074ead748e5e55272ea351a5b1607615fadffe0dfeae58b06fb61632e9da042fd3626127872d41d55ced9ac0733e94ca19f9f205c8b033d9528bd176dab9f8236bfc8616b8dd4f2a1c76024d44f0fa1d38d79456bc18a7f7fe80ed6762a9731880d830db500232b9291c1c21420e3a0c81d8c7a6b83ead8e22af58821d80ae0a224b2ed5d516c5dda845285b6e338557a10c0d45d3a9b705b224873aecb4cab54619a5476f205ff4e9ac22a4cbd92ec045eb36dca3f10a18d88105b90d6f8b7f86e348ce9a8332bd351908da29421abce068f9fa706d693851816c30515d4cf2e5c10e47f6ec4e3f24b79acf26f59c3082859fade3650e09fb4fea5a6784b9c9ebd247db4d338da417bb326bc17e99d0c6c6b3e8c76eaf4824b72ae0216c75b435c874fd4cf23eb95133c428ddd9a52d4aba079f5b405594f783fded96ef301194b5c21fad2b2199d6343043e57d40ea00269e07973009f7868d3db9e8061ce203a25c83d3cf11f189c38c0e1ff8bd90f7018e1571a9c467da7f0a3d268d9297df134574dc7fc45e37396d4280a95c3325f6c7aec54781072cbe859f700bbf5a6e5c1c4f2c49bac2fd5f1bd96bd283045f320c48334e9daa2f3e663a31b6e4063d5139dd5ae2102aa1c45a4c6639795ebd9c4b8ab56d68d5fada1cb6a1cca74db485acc821870790543da84e5b7d3e9c7371dd32bfa5e45618d0dcf4c3cada70fd42bb9f4b5a4608914b6ba6a4e27897f79433c9d274d2c5c00472393fc317cf358f75708acffbe98ea5ae69dca54b4c2d7b41ae2ea186378af3673a7b0d993cb04a52f2bfa9167505338be7501fb6750e27665bf5d27b8a2d6b2595f139fbcd9eaead685abc2bda56193d9cbb64bf2c80ec2b0cebabcab0a4ae2bcc76e023306e01d722d4a13b340fdcbe9dba48b7a940d42b55fdfae8dd2b7f165ba902c53a5bfdcaeee680569656c941bfcc0425e1cf846d340bf91cbb94ec9e2e809a253c13ea302407e275e6d279721244f341896fdd70b4307bd91e112b5b26714ce1174fdae8d0218db66ccf3dcd4270a6df9bc20b65e01c49c212411b9d93758ad619b36a4f1a6b218562157b961990fc38e089bc38033a292538654d17e9bc42a09caae201e60e45a818a5bdd568644638037dbb38a1ce179a67419678117ed30fd5d1aa5861c65027b57431c6af0e7b7f081c46472d94cfe33d63c2143ce13a3f5929b4eea2cd576ac0e573fe4a644eaaaa4bd79ae4f9f1fc60ef1630dfa92491cf445de9b6b8127243e4dc1e48d984cc584176b545fe4bd663e6de0a1bb758b6326d2b2f99dc30d5c19edabbbdd604ea7238470bb76ff75b77b8c6e2f5b573d3ea6fb0dad5c6ca09f83d3dc5a7b7f80eea3589d22310f1375fb35b84c3b7682a41b7582a85eb683bacdbefa567853fcf43a0229358b9575488022c3a985dd572394b56ed0eb45e4011df194c3530539ebc192f83c9d3486bb35a47c088782fa734e44866342f52efcdadc32a70a5bf7507d7e501c73572187b9c8af0864285364e6f18bef05f130436cdea12dea97bc133b4283624278323d3d718b6be42aa0d9acaf6f216ca2af0f2eb3467dd60ec10eb25a95e502d4e78c2ff3c39e69acf027c41c65295c0afc10bc71727262a51cdb009230d985c6c741a8e2dcd03d838bb6d55c73b55b0638b3586e12b03f173710a399061f24ca8836f0c2ca40f95d24ebe2d493eacacc58261f04ac96e87404f38b364b3c25c54fa64d60a69985fd40e4ed95ed621d3f90951c105224a13529874b58c4da3aa91591ead26e649ad2d9713be7547bd70e5381e1b01deba093bdcd1253b4c58a4c7d2813944cb03ef4ad22777b2d164c749f701b901a26afc9da94b958d53b557659343b1916f133ed0a0a1b68cefd6092284ace941add3a0ca82fdf2f45c26af141bcb32fcabc1c821298001e49a6e49c145c9cdc23fab9e887bc999d085744cf19c6144de32431e09389be8250dc3f106b135ec02f89bd496d6b3d3ddbbb8b2a7a0ab46e76d6d07eb76367b13b414422bc5fa387514fb622cacac5ee25cb41e30a343726c57464c05746c484a7291fbc561ebc26d2d38c78eff377bbe4206ba04ad66e6eb25b76a348859175c22052425224c02177051c3116157cdf7c3e89f148b0f9e7bd578a55ee655ccaa5599ac6d3857663d4424c7082aba5fc76f1f107c51797cfc5b89aaef55172c8f0af5b1336025a23cc51e1e2baa9c7e00d952353866d741f6eda37c2d4412ac0b17380702b4d57075ea9145061f7143c654bfc1cb84ccf2734ee84c3bbd072e1227ed22567fd0aa36c91fbb2ed6ba25c407c81f4f055d374063b7208a55ab369cfb849b3e7c4e34002cbf067943d3eaf8a6aed8e9ce0139864f8f2ef780d4dd087adb432c7e788e88801839b87f89054a9309cbd2b3a9c4c0752a12170bde9f44beba7f9798835a848ed8f2718f893dc912d227a24468f0217b0bdabb0f845aed8ffb2a50d5f8c1207e60694e53efd84af44e30be7f1e9daacdca0b273845c6254bf0457c99c5ecf321d544529d09e7d88f7aeebd196d84d901ba1d70fc82618779795752c6f8887d24a8899f9be768b97632656d656f41ef099a4f71920d8f73e15f1278241d626256a35bca0fc62b36ae80639e7fdaaa4422ca8c7f595131ce92749d64765b25393b19a96aee22802c14a44d4be4a0a5034732992939d0433f46dae6911d3949289daf888c0321ccf0540b6dae9ee14ede7d784924ed2fd5b0088a710e4b06b9fb5284c1100a35f8e7424eea4a9c11b9bfe39e5691954f9a0a4eae0797e545a9cb26552d2edf7ff9fd8acbbe49b277282d8bb5c32c4749c9987d2e30f9531bbc0d22e3b5d688645b261d521393f6d95472b0b1dddd416c7f0b3ac7c10f85519e4457a76cb2c4ef42e3428c5512904be22f63275b34a554482060c57b8f643be45e517f0549a0f7b87552ecef1035027098735e17a29684a91ece0b719932f8c3029268c121bd58028f710e26ef65aae8f0fc8ea2e18756b68bf660271eed1b43e0c6543fb078415d3d683df9187f6f41472b5018b12cff0c8e8698608fcf022e692773f18a7be0298ba4ccb277a7c57f7a07d866202a3fe8a136ebee5775d052b0a4c78fa441ebcaacd32b579dc3c2f2d411a5bd1aa2ad55c66362b7ad83dc7e5f0c70883fd0c18fe1c45e5e916912d7c27d11c042199c40b61d059979ec104b6a9f0f9ff0aa546469ed49b8cea5bf361ac3fc7431f83ca62cba158384309ed415241341bc5f7f8b1200f1f19b6f8a8a89db0b9a77254f659fbf25b3e9b0a19079a4949b9f7d84fa62cb72c3a31837098a566aac82dd99861924aec84a146ec745228204ed0fe1077bc07c9eb6b792668d3f7977066245633118a2619a9b80abfe5c3080ae7d751cd56269cf06c279170e044a4dce54dbd4220b6007debb1d39570491c036fca775b49cac58b9db730af4c77ea3c7c30c79e8a9abc51fef8ce80f9a8f7758df15f8d559a4f214d5231a63032c086f484ab16119ee5ffcb38fc638eba86d201bed9131d0409ea369461fc37f13e4cfd32d5f242cc8532ed7acf0e13e45472db49629d50647b18f4888faa3bb07e0dc842954118616c9a90db2b65532f99719a3954eafb6a02714f88f67b5cb33e747486e76a5153784fab4f3f22d0f491b574d964ff96d54221482b88fcd99e56b44ae0aeb5ed24a302f88ce5e3c1a1623cd9052e5cf9ac257e8997d986d21528ae92fc5875c36c388275334018a86d97963c27cfe7607c410d7b36df72ccdaf0cc8d66827e2753d11c963a32abddea9bf0b5cbe106ce25ff1ddc49da3dac88d766531339cec53ef0e69fb932f823986361c67d9d65101ffa51b7500cf03daf328b9a3adcc3cc437e408a2a72a4a564aa983dcd764f88a46e5c07adc3306419709b3befe8b3d88c96d46753786a1273c657daffaf8e8e5c73724f5c3d3768fc5042272b63695b4dddc36a40b134d72f847dce3f7115711f25492319e4844a914d0eba5709e7e35972c16eb6a2454b5afca7e0edff77dd3b713145111a9f9a3c0bb87e1126133ef1b999b8abf28ef3aafcc9b1f69d678c4f909cf0494543cf3fbacbf06350e794e30e4d1d044d40eb9544de7cf35c1ffd5f8da13ec4aa969a159ef7e2832029369b7f725142abeca7160f4dd9ade65cec35eaa4e645018e035a24620d88708c5ee75df93acc5319b6118fdabe1bcc95251b23d4ea8b48fb161e5f8cc6dbac52407db0dda4a884d7c3033c1ba642695f217f42c26ce10ee1c98f56f34d0c95fc8f09ad141f79de80e94b6362da338719a32f6edee425d9208b6e6570f68431c0c7d5108f1fc8c6ade4909709b61e786c966bec2dd4adfa5a14063f4c27c8139c3a9f46a7116ecdb9c43fcc5372316d2f57c9abf3302991e8d6e432a08691e888bbfd3736ae0034861f04b2407dd15aa6c39bb3d063a3f7a95291e7282f178255dd68a02386799f665508efd27de85f6ebfd7d255c9630b7b42cec3ff70cec606b91ec0e0c9549a4243de29bd685739e5fe42189ba10be5cf5a05a3bda643fe169a3ffd1870643076be12c5189417d97c1f96adc51afcda9f22724ce58fed10b76dcb6426f39f2fd6e356e1ce4a9413988a2dd1519aba49b078eec6552c61b591528522902cf79f99b9500dad73805d2009bff6b06f143d3170b6cbf1f0017b56c396c3bded094d88447dd37c47a75926c1404b7ce53b617415edd606971aaa15673406c4dd542c3ed525e0ba4e096feb22b399b59be455d381494224f41ac379f593c10885b92ac15f998abffd14621d02d059ce2761992ca367afe22b32115f374a61c3e30f407446fb459811f537aa6cdfa117274f740728f289b7901d322d4c3a1ee0905f9505ff741033af2bed87b3da6f350d14e43e4809525edd3b88eeadea028173176eb39b720e089cade403af29aa08410b1cb23b25928bb0e8c85d223e39686b4d8718167f6deaf8d65a5e12bc65c1422731fcb140d824bf003e0f0f9b021e9618dc173182d5eea8c22dc1f041f1d3ca7abd063ebf66bab60c0f49bc16958d14a8e1f0b9959c5c268cb64e6f80b3e5c6bfbaa6d7914a990421f136b69d3f16856e02397919e6ad7ce1c70c694c0a333eb4e749dfdc7d9d7009c8bd9262c06dc3dd695e744c647658559e8636da444dd92624b91e5f8b90c75fedf79de5ee39a6f28f0823043f2bd7aa7a8550ebff593d76881f66fbae3c3ebcf3b13ec001818335c954b36494170c6bc59c0e22e55b2df3bf9b3da3669b7cc0d54f941a088471c7f5b543b8b5343c8b5428e393ccad74b1f0d245d8755f7cd3ad20302a4247ec3b227c1a0ecc8219609b7304a907b60a4ce2c69e2da087e15453e2994252e8a71f80c59110501618788c5f3ce930a83ddfdc0614a6764c57e71f121be8f25f90a7b329213c177ac8aaa5d152af4250e242eb7a4ac2ff78977df33d28ebce2a7d939738ec39416176e691eb20a9d5d7361dddd1970d80ec65a3dcaa3ffa595a6b9cc18b61e5fbce6451b9f63f8ee778ce808de69a609787416f8bb06fa01ce9a933d281858ecdf9766b9f9dbcc0caf736771bd29b59015c894de2ccb39d73ef31ea6e28039d67fbd6bde3f343dd4c4793475eba95485b8591a207ae5a25b30a7fdb0b44fd895e187f9a7d439c36834fe1e33e11fd3ad5e3441a54e16d8d8f332a15a00f999f59cf2596d56d7f868d7cbbf2e42db4e52fc787563edd7bfeb40f1c14cff1b28566c8ff07c7f3328997e4b3d72bd048979368cc0ba9d26abe8042903f977955f3914312f566bb3a87ff74ce5eb3acecc9dca813da39e40c15efbd22e03d0aaef1b3421fe10c3b46f8229259c4f2da424b86c73f6bfbb8ba79e3c445b5013aa4a3bfdb090fb444ffe0492dd696eca9bc846fa7f798c9ad3e77a64c4859374620c1a5b5c7eaf974d51d0be2731a787a8dfdb84d864acab449637546851c7d44be1a0180638bd4d80bd8276b944238f79b042f6dc6ac532b358d50164c889a5c0ed062cfc363de81c0e008e0785c2a256f7451e2b65689a166de0f4f612d2fefa654a4d6a67c62257024481bf3fa3d0e2501d9bbc73fbc1ee34e835af1e524037bf0cbe797043c1ef77720c032e8e8c132fa887ad226270b0915378ba2aaceb3bab0d6f806607f295358746226394380dfc44e5a48b177843b4c107f8c06a8871e59224e16a6366e7f1ba9519018ac5ddf4be31e17f343ac6a804bcaf3bfa023f515df91796912d69dac783fa2045dc67f63fff106614a19241f8152bf4c5c9cd1c553c99a45963dd74b91cb32e782468d29caabc1a42400b309cc0d82968bd69e05cdcf8789ace80c5822eb785e8488280862ef9e84c4dd4be9e7e54818054af376f871d3111af8bafb38b3d9dbbc0af2531cec31f99ddd514c691e3231659a0a60553fa4f462ceb29834fe9871cf95a2d63e51e0331876804eac14582e9f5f993eb5b4bd101e4078438dfd5b2a888aaee192e6f1743255e7e24d2f30070bd2e1699a76f6aa5422952c31de635478da069bbbb0ae612c19125ae1905c1ef4ee1cfccec95130c90c2798f05c65541f1188e47e94c11299ca16fa5e1978d2633d5c04e298f27d4699f77338a5793a6cd368884d724a0fca1f08954f6486c98cd0fe19b4aaadf822e363ac33aef0c0d5e93ed96077f872d93630659df8db0402b59a6cc55fcef1177543d8ef4ca45dedd18b9cdd0da7efab2363faf2728b791cb8823e81e003e82bdbba9764059a7d8f273b542b4f537f54134edbc19c9d2cd0d5c0e42e1bede6540ed8584f3f90910378a9de7d10d5c2e9c74f3ca244940ee94c9b9feed591d8c3f60cab551bb43172b6740bae6af0ce7a331a93cadde4c7fc5d628cd07d513a5e3054240e4916d4fa6a343789977a692daf778aeb7f4618073cfd22adfbbc6d39cd8ce9630b6f703365c4e5d4e7019269f02297bceb7312f85ba6ca922810ae218a9ba03379b9a42ad1a3416b22a9d672c0e8d8d78517721d356ed484601d308a712e6ee311c18f05fb67371c3543a0cae0523f6d2c4b1f299e8d116e17449e718430525dd8ee75f4d6a8dd04daf569da592bc49054344d19a2ebb90ac963f9ed6a9d49da00d18baddd229ff014eeaf50b8485fff01093ac1f5d4bce7e657fc5c428abc9a81410c3f445dbfe6b8007227cdabb1443aeb2b31716aa41d4840a7f514e12eade968e33aba56878c7f1432556c835e7679ae3a8d42e713c6061d81e37b79b1ebe8d4c1eaa24ba84d266ee3bf70765935f08db618218fbcdd3b17f2888a1247daf357bca7ca9a17e9eb89418e929b4f132cb1f210b2f864c516c993e6637b03c08d57962f4b36da60ac343160bfdcad63b849e688128118705ac915bf1aa9b4dbac4b833b67fa32427e2ec57209982f55aee250b66dce283f2a4bbd3fad2a87f54d9f890b37cb28cbeb2a0784c4866bcf64cd98242af655da2afe8d5218385e5d2aea7d6f85c64adce37d4f4cb3af523410664017fd423900e9da5a6609b4d2d8806d765295d3f51514db0b67cf5e2b1652ba8eb30538807f59ba87eb1fe5b183a01ba2942ce40f3eee01cc70afa1120f24f7894543d741028c4f195f9150319704e57c036378d00d9ac56ebce3f04025490c21b56ed44ff8d4086d24344627ca8548d83b290dff548fba8d243b4e66376b1f14d08f59d3ef42c3eb4b29f2661d1ac9c61ac0e281aa387cdc7fd8f3eac3b9f73c768c1c00e34cbfcaacda7ffb58bd551a82f08dc0550fbcd2472eed576376cb230c7d5863f2f1669002d245926189aa3335352307ccb4f57b77cbfc71c716939c3fe3ab7df7c8a3ece0d72373b3cf7c748f94240ef5048aa2407c2a270e2b9007f3967f96eda731ce1e6bda436cec472eb4ba5d021c86d3a859443f090e9f25f1c8561f42a1e567fad5a308bddedfb84aca51cc387c6c41714b5a8a9f2c3c7a5097c683477a40644fa9de9f2868f8bd2799468892f8d36067460ba9e2bbb4a289f8909aaa8e6efffd93a9eb4e8857c7c0acd79602ca0954b84fbece863de888295ead2a615e2ef9c5e157568ac7135b5bf8ed4c6f4bf96ede83e121fbb637a0382b4be3a2123cbc6a0412f330fa0a3efc77c74b75622050b7dcea6a04dde75f2bb943b9fad3257535d9dadfd45b89054fe9db177219eb2415c169b1de127a4d3537237ed985010786c608e48bce03319524170dee8b9b5f133a8804ac07639f7c4e2ee2f406b7c746cceab98822f68959c31c4b73b550fc3c4f7fd417d6aea23348b22377d10fc47563f8bedd7a029b995b6d79cc1315c8c596eda04eb6bafaae677fbeabf4dae3bc8f03add3466f21a1b8a0d830464e7310928452eb458fe8fe328316b007569b3473b805777fdbbb5c70dd1e9d055ed87d425431ce915430884d59f7c65eb88e3f8226135a59a2e136e884ea62a343b72ec36d25c2b9bc57fabfac7db656dd226ce473af9af67d5453de4219f2d880f6803a658f5c8460880a7e67a31e77f3f82b0270e2a5a7d10d78b6d284b34eb99e073bc1a0e784936ae6559ff632367c646f5eef0ef1ef273e5575d78633d90a4b567f583d6eeb45229ae45128fba1653ff157036f0204eacee71fbf40f687c10abde5b127aa7144c7b81fd031ba130b8f1d71790c37b7b101383c96733eb81ce8827ab0833e62a1a45cbdd93e6618e7b34f7375c27092e4821302c5166d894ae05ff3d41097b86de1240fd75304acce38511cef837cb01e0fec97004cfce175fbd3c19603c092b11e76ebd76c35b524befd2b85b81262b2acd37acc371b2a5b445a3aaa7e5b401a45f6d26a20596d142ed31c80f30df8a5417ffa17e6e7538acc1dc926902408e3621f9566edd1036a11041ff83101839211607580ec2cb07b3620a04eb6e68ccc054fe56fb445fd7f37c83f28599458701391f8059171553857a5177aeed3a1471109464d86d6689138ab578874591016f78ccc09c42f59bf35acebf2f1ebb0b26465998f8134c695925889886d00853f5500dad5155bdb1225913d4c2bb4d1ff4e9de724dd1353a647b90719290f20cd087e7d9aff04cedb33988e83f579d32ef826fd639c669f34ce08afd9349dfc5ef0e2c1c310eaa8d2712fb2816002171a02696916da500641ab97f139425e19e8d36dbc534fbf0bdf69dfee271f363e64abf1f2dedb2a33eefbf715863db079440faac5c373a831055bc2303ce9afdbcc329bf973e105816b0f8b04d68b460ba4cea0c65a53074bd1e1c85fcb8c76e8d11109d3dcda74ae919ecc644423b0daf8c5bd98b4bd10f5e87f12ea0eafc843f37683fc030c1d59ad43c78dea38604da084f8595789a9c2f6a43dda04fd628c7e3e28f8ac60f74ed32231078d37cc2777904a4fbc84dcc3d86ff52b7c4847ae39898c5c31190b1bc2cbf063f4f7b61dd013409e617e0333cdb48bb42a5e1273c0275ad4b470abb42bd6643e98b49d29c4e34a70a55b04652b0bc10685b0c3195ba55026773bf863a4b3a2b6d5b86149ff94e18f248fbbc321681362848704a20bc965f1a98153436cbdb909413a637d94a5b4d3a1485fcea577075fa15f8e94670d0e7ef3b1776b000ff908a2c737479e4ad262b015863583cf772c7321567c60a8c5d1f091796d3a05bfa5c364f7cee48ca1b2e8a1a3cb950178a0d1c49b10ad411f9717f2df012f89a652541a1abe3c438efdf3a0e3067c4a53fd76ce9acef96fe86ee9eabe9d7ee98f74670b004ffbfdb98a261e6b507c41ee2d671ca178e526e90df75a15bbc2e6286a8fba1d6240fe68e94790733633eb87b63c77d2b32c0a5e10d03cf3af1d1daff508389e8b86dc23ed75efc2fb51bf77994018bcbf88fe290df5ca230fa4efe74f6d58c8dfb1536134e292732037cf31e817e76667a136f461d10cbb46b9edb5c51867e87df16fd16cdd0acfb7fedc19c460202b6783a460381346cd9438441fa421542410d1f68c98545081e95ee5d0bc4479d292a33d95709d8b92e671ad6dfbd81478e220b38ff6bcd2e64577af587d398cd9860359748ccdf3d040d182d2a557bded979b120af49e36807c3071ebd210ac00934de579d3cd14a8d9a587a1200c5cff26aad53d56af1f2f775b93f7f9c9e7e4a6f98fb2656a625ac2e8d03e6997d94d4a5d6b90598fe4141a13945caac251875ddccf7453ad44258ef796ff9e6294c97d22af1a9b19a86178978f5cf4cf1c1feac4d8bfb845a7ff9196365eda4aed875db4996423f236db4d1a383baebc186b9671bff50b0a828c9c972faadacd329baf26d4b5ac3e9b19801b4611ecab0c97551d7eecf5acd6afcc0821eb77628edad3d886419f5ec5a63384493f75b291ce82dadb9a522af43032e042c4c569c92e51d53697de574a00a5bba7bfd7926e743d8af99b55b6b23893f1f39b73528f4cb6750df4537def8b2032031fd1a1295b61bbdd89b8c3a426bad275c49c583a68cb40e0648f1d11cda71dbd0ad8d23c7f0de2b0d3375d7da3237344950e997ad3a098e9adfb591c13cca4554af3ef915d9fa461ead38a67d4d42431592725cb80b0cb8f3606fb3ebb8e9720ffdd684d32ea9ed71e1d7e60fdf09e53a8a55433e226c0e7e15de9fb7cbc61e731c76ac001e9e00f453d7f810d2226c7b6ef807106dbc45080ce20e17609c7c2efdc70d85f1975e50c1117283a7130153fde7e384cb47020bedab955035946561e7dc833ef7bcdbe47fb13b1f5a1f64f449fca141ff320e66d068acad70ed4eb01a62d9b050e4e889bb77dc9363944234dc42746c52e667577b4b55163a5f58ea15153c134da6b8750b5d9de1ad76c785fdd5126a3be31d7bf790396c503dd782ecf30c1b780128123a27c957f9f5ed6086a81b792925b834eeaccc8a4500633108d3296ff7dac68332bb6c8d6b3e4e5c5f3e015d711449b63326b1e8dd67174e863eaa224399cf33656ec1d2a68136ed8efdbb68039c0b74806d2a25f0e45ee06019678b31e42b3ce1faf78f9e789a18135acdd635748a8a57dee5aefee7b02911aef06ef4f579c4e6d7e94bb2e9d6d637df499e87d951e951c45fa8d55a3ce83b2d3fec98a3d29d2639173393daa7f672b2b16ddc20e2af1768d99bf24fa7b99880603c6d0922cc877ba1986696a1c65dbd1c2fa4578bc6ed0adebc13485c2fdfb4ebcb8749d7151ce76080bc34a13d4cb0d6afb75a90855900a47ba6fcfe07437ce303189783c6bcb5a148d96268a65db3aa62cbd7892518f4f2de95356a7ffc2278bb09534faab8c712f668b2cc911a88be448ab7250f599e8188f03a767f57cfdb7a61062730979b41e48af22a5ecfc64752b612a03b13869b318d2acfdaffb4adbfa978634c1f5951b5e8eefb7797e58811c127777d7d48a79b935165eaa2f2165f1fbf6fc4ffa3c3c100d5c1a0c9a08389eedaa7cd47815148d7526d35dacb24faa99d02a60e95a7071638cc218ed0a5c5a21d188a68048b1ee6af75419e2b9baa0985abc7e75e490c599a11cea8e1497dbe63ae47b77b87b1dba3e4c1974626d081a04a0fbd0e88c5f5ad53e6172a3f4b069e5161646f6dffa971c60c2c9b033e4665010bcf7351221f94bf6f525558f491b1ba2f905168e3b11b41c316dd54a06eee38e4c42d86216c85479124a905e838a11ca215ea69d20cc1935731c470d7daa9837579e4e4dabc33ac569ba5dc50c1ce7c06f0dcfe1b4213c96b1a576e21a081dce1bb72cba9487d2845805deee3d85b689362fbcdec03a2dadef4ce1d5145493fd6666a09c30e2d310db9878b2b2d4e5aa13572b7a74de732f3c9706c071192322096a5d86505ca278a2379edf38db2f73f549664b42798eb97236eea1475a2582aef909163a4bb409ed41e766f1186a516777ed8b20b0bdb6c9b91c7b26ba7d7db7a3256eca11c06d13b639315c44ebf6f6d2c652033228c800cc15e4bb216c91952a63abd3684da8a0b26bce6b6cca8eb0cd9c6d14305441ebfe0b75e2c67fef2f2849764141928cadd9c33761e384a4f7b12ef4c8f6008c34d6b95a1ed7e6a826126f538a926c145ccd6ca76d0e9ffb7fc6efc85966a0730c3fa52ac0144f283d3092d717d37d8d2e85a7a7d6906e5c1ce606292f8964508f56aa185d82a4ba7e691a4923bc7c32fd6c45f926c49a12de829b3822b4d0fe8e71ba5e9d710c8a1bd596a3601c2b87916fa6ec885cc17e426306fa01470cd366a8318fecd5b6b634fced61d5e9a4732b41f73e4bdc0f7068a3632c283827c3009d5c417cd2c6ca3e7f42f0c3f890a6fc612b01b789fcae1720d2f0e3f7fe62fb60b5ea7402f992ee2e68bf2dfbe6fd7277ad8b6e41a120d56b053b2c89ec1d559a3457cf821fe4b970527aec5a37168ef01836f59dd1f36e910ae3c6964ed57fa84b48ca0e98207183dfcec4addda00da0f9dd56deb43402522c3db5a30bfba5bd95526aac8fe9dced175f9b34bc987c6d7e998948fae74a4cd0acb779a2d10eacf3f194be672c29519c38715b22be6ad659d16a71766d80e86948e388c15ad58006f673c7a5dd3d43c5fb691e2f84188a6524f8c43ba7095b6f44214c171c3944976bded6893c8f5b42a258c118a2bc01efdd5617c11ce9dd084da9ebcc4d7d46abf4e3077288e0616e1700879b9248480ab2839ad92a6d2f896da9c1a5fecfe874631c35cbb94d07b2722a6d4cd0c2c0980bd99599bc6423d6c40cbe05d7d1a5a1f1a0e4eecfdb538a95876e02060d93e4ee5c1589fb7b7edb8f22a7b22f15494a7bc723351b1d655a5a241d51c57dbaf066078ce028b2125f526cf4d305ec53b16ea1a93559915f43a4fa687ed751343b2e28de710bb1eef1beb0c1c20435cd7efd3be58677a6c2dd93f5bd835330410fb45efc4e3ccda9999649dedc0cd93b32a49b702bbcc6601b6908b9a9901693ca1f8352be74c21b58b36ce1038fe7391959d8733f0c1f764731148b9ad5d43c5ebe1e9fd92fa415bf37338d354d14e98344f4cc8a2602dfcf46b5b32e443e42b8b3e5ef039276d0bed27f92d8a5bb94f49ef4636726ff2f44a1e32e7a3aab23b4eb256d27d2899a3db4b50bc92ed1605f6b3cf1f330711d947ed0bffeb0c1eced31daca39148752f1e2714562653befe69c741ddf4c8c2d69377ba9515df166175741509482fe61ad012c4928a4b24bbc9978463a40702806041617cbc2844b4af70b98f97eb6fd8ac1f980e75b422ffa5e1499681f482b2897613a428b43135d0727ac3e6a6a2ed283e948ff4f0ff12cfe17f1d95af94b12e73410dae1df476c235607462a446ec284cbe71eb569ead3d3c951f0117e2ef44d63f91658a598b73c9c67424c2cbb9e59b2ec6acac695471a200086270e38040eae1d52d569d3f32735a4805e41804fb04a472c99545f0676a6aa669972d1267cfbfe5056a3b91240d607872e4c201168bdb8a0060a2c5c1e1765eaa81fbdb209db897be9a5d153ce24c9ff0e93a931b6e19b5fdf2e7438748d8fa2a21de82ab83e6a1f5098b29d062625c45bb011a5888584f248ec2634a5c6272b88473807ddcc9b2932965f06594ae907c1943ab3cd1feb1d95552fa889829fcb7d03b21c5bff02f0c95691f59e8c0845df7384e5bfe487a0eedfc5ad6c391460113578f8ea94eb24d71f9eff095f181e0c2d6e9746a0a1b08fcccb247294520852e8fe99866ab7d0cc2afc45c1436b6724a4fd0f2ac5e71458b017fd209c2f4c9e9c2a75c898df68fa801298028ee0be5ccaa9f86b5923cf75f543d752414ec70a67308582dad7e20a1c1dcdee6521ebc06450242362854b77243b5b78f1f082df49bd0d99bb9459f913ba5b3776b4843280e8ab13c4fc268dd1263e62c90e658def1ffcd67b7dcd34995d1f7045c3a89be972484dd69de7ea3b8ede3de8805465aa18e1cd5adefebabd890766a4c939809b3021bbd65819824fef895fa5c1dc1f9c04ec79c490e5fa8b0620ebe6711b3673ce5619ad470600c60e403d19be78ffe17dc7204d04a059ed2a6aee2a44e33295de391660c207490fb613e1e3d6399e14b8b93f8d9088b0dc93eb96fcba9aa16e9aed4d3bf78d915239eb9bdd761b51079a7c1a52cfa35da81ed06702bdaba4cd23c22549ba7f29cfcafc3513c8e580df2bf4344b86f633b5bca6188a6de896ef1478d5cbbf0972b041c86be9bdce01db00dc0ddfcb2d8fe37f552ad56cb804c22988007f9f90c2915cf717dab23995a8bf1dad4cecd77e4f83fd553012b4fdd94aac13c777ab464eb04b79cee2f7213772f3a9a1a24b308a48278290eac14364f6341da057dca5f2cb370e9e141f001341e8ab8e749fdf5e3b951f04302b7d89f493b2ce8448c2e8ea56200b8a4c6fd91ab392e28018db04b0ed1f5c82999e9f4705422dd4e5ac3af00865aeea4bfa0c5fe8bd6883259594901d5e8ba83e5fe87515e759178e271987fb0f209aafb89345a4a10e9788e8787e26cd4144ab12d4859efade2da7f515cd958213df034b0e2df2fc8468cea592d7d474d2d90f79acefb6071dcec57881b15ce02d5e220b7347e88623e2264fcf2a1827c4ddb7af56ae929d4d776cc613a4333f3e70a9b1387915aa5b0dd3820ceba2523b216981ea236754698af61e74a7e98276941cd419ef754fe9f9e56407a5d734c5c672b059b0b509fdb9c43f34b8d54455054e33677da6cb3bd3bfb193967c9d928d69883f59af61df865ce5114a6436325be98f56f0676618473555f9e5437312ea70d526ba568710c2af7a5b71c198c86b5b76c1b401f14ef2aaa36e2a12b77575202f57fa542ca8a12d3955171ea3b40ab575e17ed41e14523fb45fa3bcb69ecfafd51be36b4b1e9d0d9049f8464d957d923a7863347d59b56310722cdbc8e9216b6cd8453b5325984f7b2dffb7f15e2f2b461ec44cbd343ee120c1cec2cd28d111c8edabf98c41206e5b97bf78dcad8f421e245f27939f78aa368c572211efd96a009387127b9ed6b2b4cff8b20f7fe105b24eb3b3114c032091f8722385d8bd651e2853db84c6ab0bf035d56d8b06ff356c57ab96913e4eb0aae143afa2369c5cdba61c4bb7b65488e7a6911fc272bfb7ce3a16b5385cb556ac1af63d810f22381f8e1e77ad0a8eccf7bf18dd4e901ccff6ce61c135c2f862c70faee91219cb25ce43a65bea0c524bf47da4a1aec27a40da98d17aadc12cc08947e523e84e70ac6546e69da507cb5f6ca04752f496defda7eb35841b23f675d1e89afa20aff57c9c78ff9eb5a1a862b59606d15b945e393c2b2c01e93668ec22633b4b1aedcf524a387c312764145f59a755cd4c1dec32d41a8cff9f6a0542354c2833c5de831488ab4e05cf5b2e174138222cbf30f90ca33c390e238b89d577b303c7aab7824693df82e81d26f398e14f1f7a899001e90c8612802961052d6865738fcea12894200044d904466e6e99701b2b4e70de3f69826b77a1bb10a8ec00072a39072327debaa62be8cb3eb0df95302eb8f3deecb46a5f5573963dc654940abf7e89c7ba3c3a8ce398d2a5635cb52dd5dfd0b9f18d326b4520cd02f6a700f5cb30df1b07a60ae3a5a9e20315a4d39e6d8658c8cb61179d50fb431573fb017712eef7755de9adcd26a590104089a7ee8f3004dca7d13a1c3f8ece08702b6677ae700c8ae77083959c8a5d967e3f3099feb41bf08db71b69d652c30a1923bd855256578ccec57e282ebe0df78b3245dcb0401e4988c32a9788ab4e83e992e7b24afdf64cf389065197bd9577a8cd6e5ad1f67ca92e43624724fd53524761ee06b22712fb19b5271461dfd98ce4af61ba7e5dfb30695718d0dd2949d4acb84aacf2402a9324ad6bb35b7917a9c6e82e78b0a8306ab3bc44b88a5a8f9824e1bac2634fd4030a455e20faecea25b81b1013b8bf39ae4950f25d95fd23653d30e0e87d9f8cb78665e95e269f487490b1f5b1b5e37ff5da46944f092321592569768bb77321a9034a8bd57835d0c9b53e8d2822361d446c61a635474c78b160f00f3ead288ecff1958498ac8d6c9c820461c48c67e09481d987682ed594d8d1f08ec9081be5c9736e28dfa0b04ba2fd1484f32ee5ba06643254e01caa14dec31f93d9fd51371f8ff78ff64619742acd5a5dc92728d20ba8d2299cc32b1e288b8fdbca76de0eb3d7365d837cc73315322d78a068acd9593f81dd72372162440fe751cf2fe6dbb339684080fb6a8830d4cd63bc688f236b4c9e69d9cd893f3a2695a0be6c30a2bf31ee49245ad4d89404715b5e9b93c0a7dd76cd6cc8c17b38ca6fed69017ff3e9b8e5893ee18e22627cc7cbf73516d73342b576fd51c8be4d0f2f0c1da907439948b9b39bc33656240ac33ee1f57669749e37588b4d57ae76c8e9044236949b8c65b7e34870d6cb9a4b8308df59106e31eb3bdc6758000f086df0a7138494550dedb72bf5697e5fe186963eda9eac11f10eadc70d943f4cf9a19ace7318eb69390ee6ac3c8ee3d43283058e7570e8d7b59b3c47b5afb7cd5ddd335b5a3fd6ccc15b5100045656b1fac2fad4b0637986bbfb7bf8db91c4392edd7ed8bd3d8becc5bb5e0a58ed9918803fa749c4feff508927576082aa884a7b17bbf097e9233cf28ba24ffe234279352ba56df7a98c127b85d36914fa8cce0df2293f5592e0aca106ffa08c263e217d4642f77e399cb4b3617d4f3fe61f7f8837d272560edfcc934a785ef0c1507e27e530bc4d58acdae7b01651fd040cf971b32e01db56c054470a8f14d877f304464f42890a90f6bf36a9385d66d360cbda938784ec814d123ffa6be8c17405fd4c93d91015f6269dd742a2ecffa4861d1001a321d8b6d7b51761817405ad345fa898961e10f18f1fc44baad46b3aff547e2bbae2f014832bed27b31763f4e978d58f7a4d1fd2cee689b17f610c1b88a8c1063462419bf97c56030cd954486ea22b030660abf07dac6a83a7535d6d0aaa7ceff6501a9dd7cab238692a4ce06531cadd1ff1d2837df1d50511e6b7a550e9e1d7714f0bc448efbdaa2e41be3e0d7afe13fa8fe9a9e27deee5d7bbcf48b500d52df665ae9966d0a24b462d5208312638dc714d41410a9e4cea3db142073afe8e63f9103225660597cd672ce3dd018e6ae4a83c0cdfa190522e05011c566f4ff4eb40529cae87748ab7e471ca9c359766d2ba262a0a0358d3c4d1191b4404d01838a613f29824a302dc26200eb769139ce92dd458e085a944ffa5b20c57e8adf1f6007f430c084b7a4abceeeed0ebde836e285d0fec5e6eb981ee9787b3982e7237908aadbbed6ac3acd03a0c11fc2c7b3d7760b44a99963aa9e9d300e1e6819c7e50f4b8954ce3de70eb8a8677761da36efc6dfecb4a475062a1a72bbcf4a64c6768735c366b920e6d0815d4a703bd7f3ee6aeb08d5a3d3e79256a0e03dab6c5fd3d9417f802d97d7514342d5a59a153ffd5e76d70fdbd643415c40548e9bcea88307ff71bb957e25c8638fbffad6c212ce0f8d86cb78d9b1201142f182eb12307d0a5d528d9987670d4f8d345e0df16f06af77e0b82866ff1c2fb2b8db5704d7212cdf6854de171dbc808851c30ecec5aafeed18b1b68671d8f313fad94ea0e0dc990d7422fcbd8785997458454c79bc433470b5eb9ce0e610d962cd26069448b93672373f3a3b6a8ce9eb3dda3bf735fe7b5914dcc1a49c91e49ba680e389017832fa13042e53be27f8eb3474f7c159bb5451195cdcf8522c81b9e6cc683092b44f7fd7acda33e21a30c8da020123019e51d29eca0cab55b287313c87078695f58e34c6b77dc016a8083846253e1d84b1b431f71040f21e6d7ed2434ea0c90486658e93fc86e1f9653e927effb46a58e6ca6a354d151a2b1719d512c73419229901d6455cb1a7e50242f26a59eda90426b016050641034df6dec2726e5193c173fd10a99be350a4b3149159cf86579bbde455717f0a0b87f9c0bfa562789e60135347c04e88465a033b0f0440a7e8447606f8abedebc8b844879aef9b9da591baedc58646f3e42a8f83a71aa459b0ce92a87eeeee8158c6af1e7df7ff36b8eaf88d0cd524cfd9bd1cfcfcbc84d39e20aa282e30f59ab5d601e4c1d98b3c5a22efbc5100d8d53b870d0fcf44ad073d28643a7f8aee3fe66dce651ce4cf61eba3c42169f6d4e3262437006c833774c5cf19ef24c6f091703f43b52b265343bf251860c084192593aaece092186462263a7fa368c6a72dc3bdd5bc02594f840eca8f94d79fe1e97859a02899a2d6e2de30526d2590d01a34d0fe8f97e59f0b867a88d78244abd8846ee84bea56c5994e7b1510bf8c0aa282ffb55656b25cddc8199abbeb95dcfb157fd0822ce61efb014419ef34895deda84e3d35021bdbb915de657d5d2a4701067d99350c5d99e863a1942be1a15a5175ae5c2906a46784b74ab95092a56bfbb6dc1f6775e18049fd5d97c828bc402d8a01f3171ef1e6649cbcf2b8bc2cc614b909150f043e6a590ccee9650c0c997875a14a907ed640318d96f9006df48f7cafdf2dbfbc0ad0ccc431bc75c44ee538fe328715690584952ac286f13014274f6a4c12a1f950fd56c94f943dd6bf8a5f059646f5aebdf33f121e228299620cb1e5703867de623aeb76f1b7a257710b99c455826c5fa1f706dc11d64845309ab53cf073704219b994f5098e7268b6e3ebc1e5134b29e325a9bb5bd92c20daa0b02902e3a88d9dd293843398e577c57a0ea8ae166f490d0ee69b108acccb75630ab366ab86314f4d3fd625aa8ce3f835bc4fcadffdb3b135333f94dd08b72c26ab248c9693adaa85bbd0f72fa4aa42ffe96d408f2a5a8b56247e19a1e13f41a976fd22475017885266bc588c1656b2ef6d98e6ace8b5ec53c1fae873263f355502bdb6c35a94ed90d9419d0b2e6024b2119e56d1d541aa26183346dc3967643e5174767a17534435814b4e6b2e9cf3342def90901f96b3b5d53f418d00eb6af5da5c8ed44e76351083e89ed5a5ef5ff7d319d9fe12b10f5de62153801a31cef0999f0c4a9d1ee5b7dbdb03a0d43510ee4f77b935da6a43fc85968b5808ed578a0ece72fc19825429eb1231b3ad71a4b44b6d23589049ac22c2733f5f6dc0abebce1348c4f3eaf46e048982485f0b961dd07f55170fac5cc0bd677d181ff3aa54aac2d9e626124633845faa045f6adc3a788d7c2c178c0c23722e83eeaabf61ec856eac9e8cf16b6a0187b860522b9a7c0f7afe402d48000459f13c85f349dffb68096c13b6a4f1fabfafe3fe9a9c72e1eaedabb859311b0f622b35a7b68155eaccd6070f3ba317d76f9364444abb3e4ba285bd86db74112acf63d4ce546918d45b3bc0f66375e93439b4f4fa891835a1aea212fe6b6b2f4a8493440fe52b3d66a82a5cf4084746b556fd849be0aa65ff67d9354f40b5b8c34ee70cec3476d1dcebb51db4e010e1fe1ee374126f4f665b25ea27cf1889e4f4921751ae90a2e1685e6d3c88e6dd252dcbee4ca207ce827afb8a8652a5c981fcb41e0ef6b9ce478df9889609382f892b9a6755c73d27cdf6e8aff6139ed23d790ec869e67e966804e2470c75be41f7fd0ac60adec6c45b5f462fcd748600d776910f22f3ba72640fe3ea0b87eb3f840c71db99e03c790547f2737ac86ec16732f5db60f4b1327755ebcf8bf0cc12fef0e773d82c7f6047a8fe170aab8c438d06bc8f27f8cbf2d486657bb7d46d52b70508fd60062620d60be51fb228db87eb6565bce40e11bce73f97eecf2e4b32dc768eb18497e085ae8cf4c6425cda3dd8a5b150dfbb0d1d54af8c32125ceb0209851aef472e2089df6e2893b6953f1671d6bd427b581fca3a9596b3d92f40f28673e617919a15f542ab8c5e7e6e9df662fc99427b2cfae2d2d570f14b39faf2439d30e2a3197ddec6fd94f2aee23b17da9ea1f8bc68e9949facdacc68a4542022165aa782b3f1ba6ff8a938df07078f72ce12de3986348fe9692b2f9186629ddfc857699ec4685097ae0fe1243c2603f3924be91b7249c96de26013c56b920c50de29634c8a81be70498269d630aa3015392dc3909130f891c5986429afe21e62c60074a247087158c2419a29897e3c3bbc0105595b4f9dd466de9c8e93a08a319de46b6115f4bd0fc7c7222795ab3a975aa7f195c98b9ae136a2feb60632f5d27b369df56648ea479544059b473f5d7ec31d8ec364ac6090f1d3a59f03892c40a28260fe70cdaa44416de1816209ad26b9ae72500c7081d4f8ca6f6b53c8b042730bbdcec55e8a226052f3489e0be85d8865c55966c28c80d993dde0ba888d06e959498f13a9c2aa0f6e1645e06e868f5adc564017d30781f41015c7fbeaf113a5b2b19d0f93700f952d32eb778e12783bf76e6d02b9b9e06de369ac39feb9faa0f5059f646f0cea8f3066c4df739e60648ce9efa506fa871adc1682fc6232cbc0bb2061eb48c8c4e76c18ad7be343631621eec0fa51bce022b0955eb5897a41c760ec59ea19e7997e3ffd375762fa4999d1272b4435d1e480eb0ba601a2e7e21b9e6a5486014fecdd3cd3d85963ad1040d0c644c5153c395ba8ddefd906d270116d8d33a155f1dedfef8f63f9c9aa9b0e380778b5345670e55b30d0da46aaa6e1de23f94ff47690378860973859692e58b8962eeee8431d288bdfac486a04ac10c85b1901a78bdd737064900daf72677505688c0c721a8987914dcc9db957e9c6e0d45a28c3d939cf91d3e64ab78b6c70fe7c9af7c265502eee83cde13e6fb7eaa44dcd867591b2c86b002cef10ea09d82349b08a9b4c6edacdeee6975f75ec8e5916234ac16feb9796ace3d191ebba4a043561d93b96e571abf79a997650144e3775b7103e38ee8dd4d3ca4a0a74d404c2434076937aca516dc12e0495e8d404be0247beb3789e80b23d99b6f22f911b904bfe5881a54c7d118a965ad43ae3f17d3b4b36ef85b87de99748aff631990db08458bd30f8dd8ed8ed5e3eba3cf4187eb7c3609dbafebfa74fa73805b9a22aec2dd2ebd0a16d214cf79ef88534a03d2674053e8e405c5f27213115da579bc1b8669345f65082fce6cdaeaa26833b2f9460baa84663575915061fb7e5a205a74400da673aaf1d31c8756c2e0aefb2c9165979ce6ea3ad4a8b6f0b352440c1e85c9208c22eb963781cc2a8ec558a0b2321bcade66189c8988db5c59e9dff25e89e395b586b23c0f5b04fb7e3760c6bac8e85cc6160fb8e3c65a64fdd1acb3c9e4bb91668043e8b3081def39bd58d5e4e28a0e077157c2a65f9b792096d0084b22852914a852bb224da2f95bac3e850d6565a4bf4486a67cd96474882bf59fa250e179221d3e970e5cd6f5acc17eec4b502899d424c20ba5130fed2c6e3afeb1047f7e31061abcb6870fac5aa9f51bca81bac417d0d7f88dae460418b1cf67ef4a6d16dde7a636359a0331b7ec0524e9489bf6b72a49019bfbe1d4e40b4d0098fb53f8f542d54a982a18c60469cceb25b052ea348b9f04ce90fe6ec18ed95214214e1e1c09a9fde7f8ca3e817dfd13cbc1935b7e153eeefd747c553c9d0bb7f940181de435f1261ff632e85b5cf7cb5cea0287a9711df868b1707c961225df3959b073ffbd2891af6a0a768778af406521ed9a4574373876fa66eb4aeb41ee5561fb3bb7f4e9f057fc90e4e6152dc304725908abdbf9e08876a6b8faee1fb68232be83cf6e3e027daf41fedcd38b3ed8b6832baab6c578263fc5dd7fca4400e0c821c22b84fbe5edb26ec9084f68bf5d4df7f9fd1d77ab44028314194ca79eabae1ce86dc11b5f569b97ab02184953c8bf8d011e7aabbc1deb85fa8c7c687a01d813e12532f2518e46f6901fe00fc61b3b7d04b8d9546a81129ae69e40b1a9b47450551aeaa174c13637fd597a3d24662743aefb64902091a9b7e6519d9316633f9a4de1354bae909ac4ff1b6c0abda8890dcdf02ba089254afd2d20d1181216ab5d9e7baa0156881ff978b3e791d1a5635d5838218fc73eb7db8d9a3bc7f261ceabb2ff493ee3b1d096a2ce5b646fb16fe86d3db38f739ff02daf61fa4eb21444d31818c7b94b236238b690df9b54555ae76e0021e30852191adc388754a00b3f4a60f6da5a8a6f760671d9908be2868fbaf502cc5509e3ae22f3133cb6f41570d2edcac64fcb6eb52e563437819ee538de29ef9e8bcbc761aeae43bfc8da746e61e156e8905c28924847c11ba9b7ef7cd9f8529c0be841988c2d0da962335b7e4a1708defc8b03140a03469197dab681f76a59c509d14ef49ce09e66fee4c99edacf333aec247ce3075a7f99cf01eefe1114a671399299501598bfba0df107888e952bc8274a7ebc93976152c6d3a5604ea12ec986159e928f96974ee006220bc748ec0ad70951b09d5c2cc41eefe1ca41d1fa7e22c0da28d77d9f9163d5f029c73546ecb238baf23e254711b7d67f5f3e455d1fd6148d80b634c825097bbf5eca415f7059271ed81aa60976185afb3108a0a97a721f72cfa525efe948ca1f14db0d5e71b8fdc3d0a4617426d52f527efdbb5fdadc60f51441647c688743f8a146ee75620ca67595b5c3f4b68cc8b5c268a0b358b713be654c14e8646d8a918ff77e1fd4f0ca07963acd2686c1df25b07ff0f0e37ae44f18188601970661390d5c4b336db767abc6defb59778a73a4a85e40654662ad4bc6a57977fe095cea696d5e4b59ac80b7e4db4747a1112afdd80187ccb2a2440e5b5abf5cc5415700acae8007a43999bae2c41f3ede74d651b5330638e7332bc8bfa3548cadeef9cc68594030b6379385b3827f4bdaed9fde389b243969169f5c82c3e53eba8ff13614f17b34e9bfdae9376dd6ef7494d308d10ffc2c233fd9feacc7d02a75377ca8e70b38ec8630fe2897588a73eefbc610ee61aab0de0c408b0cd292e521dce40854d6fd1ffd8068ee188b13b5426fadb20448cc74cf797c3470259b1f980e724e81b69cb1677ca3faa463924e0657b5ff5c604035467f7252a00a025a881aceb26c307ba79c32c166415683ef25924502c00993ed2dc2461e3ff36b92e7261874ce4a1f2532a132e3710d2e0d0f2c44537f0447802c4d6bbf4e8ede3fe443ef8bd32bbd52b6e6cc9dc61e88c952f23556104dc1b3681af4532d0d2aede33c63ee8c7c9b3ec5d99b99d30389e1afbb8bfecebcb97046db04003af6d2d323a3e2f3c0f09c3ae7719404d126029b472ba76ec4d99ad068ca3452d19f07a46e23a32a2a4288f24ebabfa8505a8aeb91b8350ebb834cf00030751700b05d10912c99a335cb2e5f7706dc8409bfa4b6733b30d2fba3b795f5b478c45f4c75b72c365e9f7ea078354f3e251361f2d4827313e6eb381176d094cc6ff1eae42c1201893c29ab607141818105a34deda4518980a4e168af3d03120e30283e9b4dba7bec1caae1c55285109e07dad8416a21ae8cb7ace870a255152ffe13f60817df71f0d6aad7740f145037bd99794469f100faee1bf719cce53e4e5b21921f084af58e774ea66711b8ca92286849896faa4db262029abb451031ba7bb8bd5f9856e280f143c3215d43badeeef9e9ad31c5e89417cc304b218a391464cbeb165c8cc76251027c0a79355256fda5bb5eedb8ee47e1febca9ae8fc406414d81a17665796487d7984f9f9cfde8db47acb579a60c87bcf2a4587b8b86372492f0f7dc85401bd293235fea5a01591fa15c3ae0ed862bd0b39de64dbcd66e8d412575eadf705260b3b5bb88e7881ba2f3150db99534dc9447c7d26a30e5556c04f6334707d2dab134a2b34ba44a2de76bc87977686c8ed1a45c8955391b6bbeaad256756d4f05db8de9b7fa6d80d466b66ce90071628b37cdb7df5529c29618a5a44e1c10256fc1af3bd3a52b6846bc3a1247774ac6c00612e468c6a15a901086a17cfaf0654bacb1f955d3b420a4bc3dff93d334b180dc316ac9097d50c4e9b85a1326dadfcfc0949dd0004bc2ec7a200d57150a943d38032726a50460ed3e0f0fba807782a25011cb12a116f5c564ab4929ac937f0affedb0a8509797fd3488d8770072945622ddc29f0debf78573b8ac0e248f7a9a5fbe1f259072c74300cd435bf1e1864f9e68bef082ae20dc450827c283073439bc2c86c4b70de59005ac4a93c8b515193b1727a47fc5889fd73d545d86efc5fac05241150bb4a6be45d675adefd9e87ee56b05cb4f852142770c663440e1f1fcd0fccd8e57411aebc03ebc9af698c7fb734571068ec2e3e04c34a47ef667a6ef391859a9da607a44c7c69abab446f3e1bdd310cd998550e65b15aa6fe459fbf77f8e9059e50601ee78651dc5a01961d1fd04954f47ea70d908ab5f37dc7259d3c61aa55d32f2132b8d7a6b9b7315998a1d5de4c0053613860d9f4b8392e11558a3a32360bc116431892fb9146cc0ce9951bd77040afc71a5135c7719d1aa4dc10028e495bf2aba4293daafcca13cdedbd74399189cb4bac85a9de94353db7f13c2a493e39effc56673c14efff778e2985f8ac5c4fab9323578aa286f68a89db56365fa7eb51627cc1e1d06b59875c56951612611370b709c5a8a95148ab4d4a3582af608e0d7409c96e91b27b8858d3d8b7e164fb05be2395750a4c788c6694fbbc4e2deead1f6348dec8327739251a795d5f9b6185d168c93cb0c3f2d4dd1b71541da1f2422cf3cb5a19eba80154b919d83c772ea1004673872d13d49040d7f4f9c879ae71b2d37a35e5ce4256d899c08c9e8334d59eca7afb9c52dd1cf10082a9dc51d128e465055157dd5ec65d0dd829c151fd01c1203210a1a3034a25604e73c3c81bc48e5c52e431d1250076667135be9b0d4463e8d474d63f278cc65b1526296131ec32a47a270b310ac0a7bc599bc842bef5f37fc35466f1e0bdabd4952be08d5d9dc47597ecfea2196c28b0282b945ff00f69d9487cb105cec3454b5311cc0fe0738880816cf5cde7ada8f7c45404fbba0a271880b113dd37bfcfed56c99f6c060f12302821c0c2d925715e9efc5dbd764581e50c85644aaf30082093a58bb5c37ddc12406bfc333d8345a080671d26916989be7c7502c5d608ed0232585eb4775fde07b23631ae1790cbf0f0a003615551286dcb9bf7389784db89a28d441c7dc356a9904f6a29f3c218eac3e85aaafd2dd028de20d23562f5a6b72807e7189628870927b0bfbfe9cb9f90d763f8d454fdf16b6e40aeb10a2aaa276a04f018b965754cdf8fe2594d2fb93385ec3d9daf33607ceec0d80a09c1d38fa2d49108c01a4549aaf4a70104a9fb458c62fb7214f93d15e9b7ea237c4e9a7314c6046290dedfd19db8b0623cf5a6abcedd06bd9a96468fdb117ec5183279099e770d37c5b21d2a075397173bfe4d484e30935606e74da28ff0412b52e727ba97bc6f7c563e117486327f5c0071dbc7ceaecd455ffec74587518585025073fde14f85477585cdfa5e71d2b26c1ef885a154de30e39869bb15148245bdef314b973216da1b24e7f60ef9a79c38fc068fe8651d60654f3be0691020c4caf8e17e75a967f37303629534deff2232ad40d1b1098ec597ca30a866c6f0311e950a9179bf20a146ced17409f014315a6ab622173c8d3f32919b54c4a82054b64b9b98d97b37f238f1956fdba726214da29d952b07c5261256fd6123275461fe8e0cdcee7c6aace2dcc81fff59177c14fabfb0cdae527a44c5f25ccebea9a71ab25c97ec40d2524e293a3fd66331dd546ac11e38f139490face70e7c855e7e7b3cf52e53d548d70c3d6bd4442b62c980061eed0ecf682c9b9ca200f3dae7979c3e78c2146b1e47497fe70902a8ca46d47c82cec92d6b0aeb912ce612223686c130de9b1329f26cee3cbfe9baf5fe69ffb625555eb4c0f3b39703277beec565b6c48b1aa80890035984db25b96fdd856d1a160e1c9ef6bb4e263be2aa0b15677ba4980c39a273347a6e08f4b2a9baa24867e40ab09352a883f2c6dd8665928c81224937fd366fe030fdb1847dc2089ed9170a9852907d26fc8d1105600e64fafbd86bb8d08b98bb61800d78d45876f958ff0fbad7dba7fd7eab70ad2e299e9d80df1e5b83fa76229a446091f3ae422dadb859138b540c4951f7f2208556c654bca95d20442c14420dc2a1b70951528e12681d7a60144477a575e7cc12071336bb29497723e9b40f390c8746ec18e3e79998d940d3483383c1eeb655a1670c00b23876270120f141199a91cbaf930955776866d684ee68dbe38689f5e6a3ca82211be4eb361bc8f6f699c60a8abebf33e4b1d7337128fbb70665f46b7226b3d97d74b3f6a963e7b8d857bfe3fa10443d29dd20eaa174fea7cd945dc98f3c7f22d7c350bfa4aa6d705a0aec6be68e3441607f5175b398f3e87cd81b6c96c58bd96f3c7cde448597cb24e51f472b10da3ac6420a12690a53dc3434ac65bef9a772d32d94629f667b55d5cc2ee81bd78416e998d0bec83f1095ed9a56ea0d6bcd4e382754c12044c66843caac0148e61add4a61edf07f31b0bcccee16ef63d1d0894e1867dac0cb819b57dd2ec275be098c4a019180b2c4045a3b0e24914531925761313f67ff439aafb7dac85feaaa16869c79bb8a369ae8746d781d82c807e438761f81deb72d8f0723fdf7c512a1abc3e318a3cc316108a41c45a6320a25511aea68da6f60332547176c9cafa2d68301f54edd04e421fd2fb7435d162a319708d750791e6206473d810ea66114e2ff0e04e3d75543f88d3d585b0dac303808f875b38e74d6424e770941b5c6fd36239e073a081803249103e447c09f41e830e49a0b292da889c627fb9f0a71289001d04d9e8759746a47b3d9c6b3bf15f0178c4f743d7dc695b70b6ab0fd9e67e83e3882fa4cac1f4ed0b6f2207910c0b63ce0e96df646b5ef9c785ed7b67c383c6570035436bdab13afffc64d70d88ac07b3e81086bfadbe4ae4a8ffd8826a8c1c73cf2501c214ac768a30bd749ebe4609e0058602b83063ac3e0b7a0a4f76f1f53fe62bb3b0074d8d3c04b43cd660ec813dc4b77be3aa54803a5b30d319a6000d501c246981bed05fcf0b7a4d5b36670887026a806f5788dd4fd4698cac3f7a8af20fdfb0c2c651dceb7d6b6cfc9bb75edc9f167b4fde2b2c980f1ccf26e188a55fcdcbecc2c4ed2dfa9c4e923e305ca784fb6ed190ad75abdab9f091b095d116818a6c16bc3081163df27f78ecdac0295fce78ac196101a76f23ec664b7e2a58df58dc6f13fd9e3eeaa42f2d535aab032dfb06ce192219eb5bb4c1259b86079c840df74d09c6aeb3c678e1fdb25c4fa6dd10f7615fb4c97acb29874c535b60ee30f257c477f903cb34a1c3d9b77b46c3b882f86b2485ce898ed02ab1e44dbd6c7daf91128d0e10ff3ec195d991a6ede46bbdf0c0c91e53eb0030e95edd4dffadf5535780acbf92996ce2f270c721112cf0d2541643e0db84f20e5e88f15120ddf1862f8e6f6bf59efa1f671cefc2e7f4cd48f0021e0fdf0179f88f71b347dd83b749d743900c30bd484249bae525ccf95e19c993fae5a5342ca24780149923a6ba4a7340364cbbb7492a4d9c7257ce9d665072a62c2f387bb0543cff22682d169085928f0e88d66124dd4309e25ace477e3252f51245a60b128833c77ce3561f34a88a9cf3cb675ec8784f6a2a8688ab4af6ff5e39a478ed242c51789093351aad05802e8d7f92487900d8cc28c738fbcd12c52fa7c53ddee5a6fcdc28c156f3c274391a79c2745aef81f38856c7c2af7046d2401c1c1a43881123086ebe31fb01ff089908ba6cd725c40e78ae8d2bd49dcfe7437bd6c3e44b02454bc5d80327623ed1f5dc50cfc5ac19b1ebf69e24d9b074f423c63920c10398c7ce6936b8bd7f71217b5c9dbff8b42023664cdb255e4991a0678f15687e323b00699a22535a090c03b61fec278b9ec1ca0c0beb302e197ad780f8ec33c3c08adffef5a581e910f6bd06dd3c6031da762b85f29f9d8ef5add5b0d38473f4e73c5079cd3ebe6e85a1205c9884ee3da764f8f8cbd3fdf40cf021629aa713e6f0a2403955e756911838f01a43582c2bdc4c9eeb44637cb1799429798d1139ad652452ecf3aa2e58809f0fded31055aa0f8724f38491528f7cd25bdec148970fb13cf68db326f4d7db01ddf5bb529385a40d496be73033fe32288502af6d008b7f3e794d5a8337d1be153f60c4e6c0fd8e44569748647f4fbb5887586ca4858feb6e04ea10dee8237eeeb14058ef736a6dea25b022dbe531373e0a59c35f73ac88b9291888a8d95c103da9c0b1ecd56f0c580d5702bd270ceb27bed0d067dbd5593ccac2f285b65c9609eae1138c93b1994e2c05fe5bc0ba40b513435d20ad1abd5d10f3673034caa17caa45cb38d563f2c823f97817d5d9d00328125c2a13542efc70fb7f5129771fbd54b4a5f75aa13d9ff277bcb618e3f969bfdcb63dfe7dcb33385fd3c7b65eb1110f2ddb95960116c200e64b63b4d06318cff00788ed463bb52be7cdcdc5bc16b47d1d4b1379cd4d3cbaa70c2c528e9c49045277713e19ddf3f149a8641d272bc935a26f3ea34ba299286552e6bf1247e6684c0c755c31e7260cc9a6f6626eadbc6a4744d78bd292995958cda91609bf6767faf83c8bfc72c6c40d2f408689766767d2c85ba18efc1227731f95da930b4c69d0fa8e50d055a24449d39b2674aaeb869ea2189a4830c7f608c8791b04c519412a54d074ad82e79b5a3a75eda0250e280295d7ad7cf0796dea83321b9c0cc197084347030a3f412e41838bbffeda56a6e1002fe8488f4875f87cbe9920861bff28acf7264f7bb1488b52203a1bf1bae3414e3541574994211f8550a79b03e02d0b1600799ada67ee44723474a39000cf9b20854f357203e8ddac69600a4d1bcc3fd07e585870f0adc35000e1364023894e0ce88dd5b4091457f0a8ae3cd85b501ad5fd279fb89ab9d7d3bf2b53d18d4f5506cb15c3ec87367ebebf368a7eb5b5d9cad3ad4d2e3690f2fa9af983e064a846eaa7018dece5512f99dc584f55d04658cf8a20e4c082b163d63b0016596f7c6d82f817b59cfd153d64447c7a1a1597c783ea53f5e90ec57b5c27924009c4ab3c337384c4826b240d644e2f4a81471b5aecba1bb38c08ae05b2626a181a6731100768b7b621c0a681f72e96ffdc40d57e5ef145e38d31523bec62236d08f5ed1a07bd2c64052bdf63a7272915f5b23f2a3a7c85a69bb5f32945bc359e80cf1e401273f93d29142a5f7ba8677e68282942f76fa6fd56dcc25796238b22bba9684b8d6ad0ef4df23259c4e0353143f7e073ef05b57ee6873e259af57ec76eadb715c25e8e4900725cac5408734351107fe9b09f7d7ef88688b09aa0e5eed770376959182313af9579791c362ddf36656038400325ba9c134cbcb4b8104047ed5691eaa62d8952ac996e8a556c7a88efcc3a9a0ac9b8049be1341efede98e8eb350c0fae29c6517eadd28e1253356ffaa1492470b3fe6d610be0572699a4e7e9706576147c4423a6f6475008f5c113cd79fe01bc3ac23a107a7700771c4cce0e049d420b127cf60a04097f830c17a5e76d52b7fd1ebac923b49dbe0967f0eea3b5c6aa8dc982bc53e59f052593fff04d13ed437f970a49b3ae110d3bb0a266e5ad3f281bfe26e332b2b5aaa703bf59f69d82a1091e18f838208f0c7cc2ec7e8606470242a05f80fce83c99093cbb2b7c75fd98e4b84852e7b5ba7e7b8ee86ff5f1c3b60d76c6f9ac1341b30c919e9ce3389f6bb4b0ab1c5d12beb586fe27a49ceebf237b9f076c6f10c3204a72efd282c59e7265b25e393625d39861112a49870f633478bb41ac15ffbd555f80a2da01983df24dfb9c91239d0f0d3a1611452a5daf4ffcf1a8c7bbc586cb2b29456dca60ec43ce2fc7589c6e58190b49a1311c8ef4efc051ddb2847549a731aa707db62ead2dd73bee100257bb913cddd4e77b901d56e3a4a0c1ffb69ef195060a516e0a0d858a553a2932117c672a6208c29f0225621bddfeedfd51d5d832e14d4b12de25a3eafec9f6fb4bf2d895300213cdd2f3e82a7d22d4af720ecb8b8806b7452a0c961e840092e079763164fb6836d1275d4ffa624d41fe8894db4b0e8fddef4e5fef805cb93a508ff2b2309ad9f2ddebbe6201fb56b2ec48102255d154cfe5363ca5a9d7bbe55957acf8774650fbedccfed9895bcee12c9aa7a8127a72bf05c8fceffe1a73ff9f22bcac744d343a1e243b249117f4c16cd497b877e08e473792a86e8a8c25e21bd7e7b98434886960a03c8d435ee5f9130dc0304937c2f49f249203e00d2409ba7d952ec7f47284d190c73a264707d6d91177a7617dbc226179354833d4f4537b30235638d3d6697d4cff7aee47be213b446ea1c97c2f2b17b667b0daea64486cbd2402d13dac380a1a7129efb5ab790d7ae6f8c6169268df58a2efcb8312f8d478c550d50c85c17d610a2e73352cc2a0dde9c559dc6786eb2eceb165fccb7e1ae09ad3f71db13adabc49bc3a9102addc2285e472d15f14b00254557d69370fb918f59e34b40d0f51952f77b82f8b44d9eed2714eb1565d0cad82467017716677af282f76a88723ecaebb27d520e7716d18139bb748535afcfba059bacaefd28895a3e10a92ec0ce78c627e14447a6ad0dac25699456d6fb8f0b4ba7ab921c6c053845c5ccb116a5caf52798229fc4a6702f5ce880ace3d4eccafc9bc592782ff980268d8e897990462061dd31aa9de36a5b2e5b07976b850f16f2ba62fc077dda60ca81ef8b829da37e82aecc7d9f2022898766c6f341b13a873154b0582343a606fb2eef6de5aead1d2a619bb426ce0bcf66208f9d413ceef1099e02b114e29be1a6c7e55ad2b9f2780896a8b41b0a8a50b61e9c7e5ba54cfae600358a91610fc1e7ac570ce6b152400699beb106d6ec5827d2cc5edfb3b6bc04712398ef9edeb977322b96c62f593e8acc3b8b848e53f21503031c2c3ee86622eca65e37b8e15e9f7c3e369a7666724c9c56ec2a58502c863b8b9472369d0746bab9309cf2a631a8b0fed34947c6a10259b751651a06174312e2f0634098fe25710949b87c3a14757b53acff36e64688c1af77d9f918b59cc9824aba1877bf13282e3658b3f8323c440c707820101ba1507d9bd31741e0eae8519a43185a65ff464dd00f2623e6d33f3c9042fa82676f33f9fa710a8527ceef2c829506e7c5a221cff1f2b9606d4a74f445c8f32881139a2754ec72f2b42f7ba0d482d76711da08cf8ad1659ea623852194b208f9ba328df11788512f9fdfbe7286b25407fda380a5a3211a395f797a1b697d6023364d027489a5fedc86c541511e53405707260e85293aec44cf90a71b9a6fa335ccbcec69d938a70114bb1428dbf7cc6226f9baddfc76f5dc821020637198bf15973c8410f63ed614490ef449675e0c87b9b5b5e8a4b8d825de967e2cf15abb67c392a44de04618866c405692f4da521eee01728cc16b49b79030637e6cc6bca24eee57686182767945e1f985b22a99b6692ae0fdedef7d428a463cb08fd7cbd653dea8b2b002840838ad33ff96e643586f30ddb27d2125732fcacb86f093e50fb5df5228f8123a34398e1f0b86baf5af37580affcc99418e3b1a4ae6f36568f794bd1f6891c0c22dfc51363afb5b2b374fd85dc4f1c1bf2871971ccac992fc0c27a5d7976cfc7ab1768454516aa71926cbd21949c9d4893943565810ba232778ee48c945844e95ba30cd98f116efd7e823ba106924bf657d20c44546a1d94cdc7ce17d074c2990b023ecfd43d28d8aa1f3c938aa7545eb9c18902ca87b011c71cf1f3324e2884a2951e3a08056280b5388f4b79bb1e44ebb5eb1a3d99a9c1c3d402aac0bce1cf0771814e1c44a9907a1dd9359874ed6c2cfdd895b3dc03c66c59299a49c8635b6e6e872335eb028011e42826fcfe9478eb65e42c33e4da3110d811614438d9bda35b77938b37908c7b07458eca8424a8f41fdfcd241c783db061d85f6f9c9d27073d18dbe2b7d2b193c767d2a397ab97f6b887fffffe673d370da7ad280f40b6ccaa998c3642115b9f1539b76313a2fe56aedc8685711f94a15176faa4abd9c78cf76622f89b7a5a54e72413e2938080a22a8c05e97efbb674c332b27a16aac9cdeab17a0d2746da73fa32e25ee8eabced3d735534e9d5c87d5cb12585dede7d88dc11b04bb018306222ed0397a6d9003962887e1821a0c05703417d7cd8b16572a917233154a09576eaaf8590efc7805db70d0acf1cc5525c7d3080cdcaf439d696c15ba55ad39f12f69fa83b3ddc669355a7f4097e6e4492b903177a98a9723a5838e7331a594da6c1dff7de99968df2f28437426d0734acac23d6155555cbd1b801c2da7cf8a1259c87f0e325c9a56a61ff23a7a41eae8391e5890f3131ffa9a74a60ead644ae3415ed08ffdfca0b67edb191575587f7c9eeaab8d2212e68b5256bc46f3c43e88d2c0672699dbbedddc10465387cb19497173ad666603e02914407e7ebf4293081f8e12aa9777ffabc54853e396ac9c9db9cfb10264da35ec15efa4ec927959cf31aa1768bf43e86aeb09b6fdd95e7c82ac1463b5692b706442e09a9ccf8b72eaa63c86876710d79f581e19458642efe016e2724bf67d88d16be36bb5938319939cea5eb62e1ee7775c0089633d1e60ae312e474a0941d75053feaad39c36c3ad8165daaff0d1f0c3f9753116f731f86037a6dc9e20c14fa884c9752940a076e5f4040f1d4dc23ba2b702eb41844ffc231a61ba277f6e50a9be782a4594af079a20551ca1e0732d343e0f17e946b71184ae294bdedae54031005a3b5ec52afd0be50c1cd2cbc56408913e3f7bcabdce54deac8080e0e4d86fc3a755886ac08c2a4764b8e614baeba623600968a86a03c7dc686907284b08d60d61a7d9002e3e41db41a1f9d0f7eb281bfe0151201a1d0de94a02df7fdb331e0c3245bac3a4c8225ce91fd8b0084e00d5d08a31236d7c5b714ffe3a49dee60c1f2980d04895ea8c5637e559a1cb32ae7a60af860af4d9beb71c72bce07af50255be2b6f32441192d3cf816932ad6cb524ed47ef46b76f6dcf223b0bf45ec7cfd631bcb5f57d7d57cf9fbb79e1855d26d8e08979abd0b17dcc1edb6a693e3edf8800f38069483a1ae85d6e4ec879a9499cd2b93ca9fcbb103bd4bae48b494392466b7c3e93dea9444d28f07e013433d2c9a6694095d7e528b777696e19cd61f4b2692571f562bcf62c64a95cc43e5441ecbadf1d8ef76cf64dc873ba5c27ed6c97b7a67b353b6a2a95c48ad9b20439917bbf73058a30d2c3efbba85f0b7739663c46a627c31fd893376dd59898c9db8c216ed24dc0489bef2e01be54a19baacb3374d02eb402237a0565d5a232952d6111c22be3560be7b3c14f6ca4741e38b9dd89de2f5941217bb1e4b834e61c2564170e2f363395f9f1ee6f66a720121ddb1b4775f1004bbeb84f5a740ed810e4b8d4596a497b8bfac6d1975a2fd8df91e51cffdd565d8df8f12c9eccf890cd78bf6c2a4987df44475a3f0afde0c24fa3f8cfdffcea4adf343e8c9883487c641604a058e3d59c30d1b417274035944e5fe8e04bd4173e830d605e3483ba0ece5e31c688cb69ae851a1fa33a5ed4e296dba7838579b11dd69e69cb99f6ca133cfbdc37cdb535b7a8d26105ef48bd0e4db987409f16ecbbc21db1b5528aaf6d78c6a36d114069fc7afbc133671cfaf0beecaa5d4dff29abbbd544f69877ea0428beba733de03e4ce539a546bf6e8c674310bc6e113dcb9b7e403567782b7144b6f3fec56df1c6d8947d187239f2793149c58257b0155b736040f0433bff1e0b5a60e46608cad0b0c2ec082875fe364b5204c164689c6a7ecaeeab40cb65a686ac05f25c75a9afe060a5cd5835d50a1701687bdade1f9aba9e60e8c1ec1617ced4168427f1e38e838d5f83093ad0761b20a7ab89c879786b05c307a3c6de06cb22a3bcc5d2081cd9af215b707a9385cd5925219501ce55e4235fd4e981d75a5afbceb5453ad6a9a03de5598ef590010c8fc6469b8cca874f66e780eb0e10d7fa222332d5a0d1fe154987f4b475b99e8c91bd177840c2bd36f1fa56940b74c893c5654a6ba4f7b54611169174321dc6ca29db79b226b993215f17e1528a159575c566e243cfb7f17080a1c49ad8749f9e0620b85be3960c9410b9143cc14b2dea1abe1e9cf1e732e0c3294b4ca20379f4b322fb614bc47c4f09fa41abaa0ec4fe640ac16c13159fe570e1d77cd5d44d2bb54a59cf6e8f37899caefa72e8ca7694a347a9b9c0110e6bed89f0c969b2895091df83ec280981ee3618d681f51c30a20f41fc3c265a0473974464d2fafa33f5c7e71d37c624ef9d73c0b5fca8924058ee5a4a91d89b0ad210691ba7b8dd1310db0a777baad489626632b2ec74e80f1328947bbd4bbf0418939db021976325b6dd3d99051c0c4adbac482f0710d2e1ffab99d2077125166a8e68ff90fbf405093724b9bef2d6a63b041ed9f63f31d01a5a371f1d6209c470481df1f2d10089dfef14b9766c0fd70f1fc1749cfe53b5b6e76bd95755c4e18fe977bf07257c98d22cae23344fc0da9990f34e21f83b9e480dfe41f7af52734dc1cb2436de2566221e356b5f76d2c619966a1d3d1e79ac3c90d04326202551e7e311b85956b1afcc80be0ae0e899f1bc599f2877b8286dfa517dbd13e2c3707718d472f47c8e54b8b9939d2cf19d4bed697a136039ab41712b5c99e18c6ab19e7459a34337f2010acfe68be43432fcf9bf7f36da797b9564f064eb7a7cbf8fb72e6fb356fbb11371ad8a8dc314f2d2913826a53da177e5679957e9f2e72dc271ac5d6e8fe88f37859c11ac712f461b16dc0df81e1f6a05542adc775d80e38db6177c1d90b4eedfaed5a492d09f85ed265662cfedbbf85ad2b1fd0c0f48f70f2dd20888372303846b104781a7df896ede81439e4141acde71da9cf0ccbba4552c49dcb20fca7f3b9fc68a5d4ffe20c8f23cecb2d6552378d8c7f57dfc4a925264f6983e706a0007ac57bfa0695e09ccf92440e90199b3268ef216aa73876682380c4567af1b20db21a555b18bff4459fcf7e6b9d40ad5283d2d654b1a14550b41ee923b0192ad8346d15af5cb851e86f59e43654267653814f7f9869afd919ab37922003c6b5acc6221f8890f84b77fc801ea49bd679a3f3f080a2b43d7d08d772e4cecf2e0e42a1d3a5d3edd606908350b8fb7a20189bd9c06ea657ea4ff39bdf764103a83405fc944e25927268ab3d22da3f499b826257d097627d872b7c3d0cd97093de958281c20df369c0c6f8c796e477d8ee30d0ebb27bffce678122dde34f2c035e15cacfeeaddcfdcf8062fd09c7769304bc8b1ecb02d6d0e7facbc6fd4e0de6e654d75efbdbdfde2d52fa873890354f4633a3f6959a12c12f26216e8a17c4048b60378467a8583721019de320e045d13c8deb190e334455eaf80263d094c8b55ec08253c60d63a9c219dfa8a6d28237a5a111d5d51648cf08920dbae9ec8fb79e56d6afbae6bca9461d0ac9fd4cf709aa9f00cb4bdb9de5e02c1b666ebcf151def83b573a73710bff25ae99d3e3c553907d0895c78c4fb04de627f840e1c6aa83a9665e4241e21a93270d4049f109d6f1ec0d55f7e0481e182969d9697bee819ed2cbd65def040ad4490b150c779539304bc7501ccf43189e93b76f37eccaff4784a7a70074b3a34f2ebab2a99cefe3f950a45b0bfdc6ff8a3d73f788ec1ba3784df463b76400a4c657890e7c47a1f2e09ccafa4f4f7674bacb3f9fc4f8e940e4d79e51dbe7239e9d21db75ccb246426a83e86f94347e32f85b4ddaecd9aa34dd532bc71ccc7dde7c0d7cd1db193d2a6e0e9451feebd958e8051cdfa9e4e59a52f55c6d698654ceab698f2f57394fe3d819656485958c802eb759500a7fc4eff04d31ce7fe51b5ca60b2ee9a6395d59b59e9070f1a21d15bf3477bbde63c9ea08d2db1d6290e21df2733d1b6ea23185842b4a87215bc11b7e175cb1625166fba37f5c28ee3ccaf30b7cbfad8a4bfea780549e80a88769f7613f4affa2a4954a9713354fd1ef55d58a5ee3aaea63e3568975ac86aab391638db1b4ed3cb166a3829dafc50ef9aaddc47f8555fd54902bf38b40e3de55439a279274d486c799068100cce9bc45a56035831f712852b76bdcfff3e7135c43f857440bce114d9a0c3112c78dbd0638887d90c2a35c8ee2e2f989cd32e49d88a317a5366f2e842622841cbcf821f5a2a896713760af51f17c5c9be20b464d7787ed1485b2c999539d3c1e6031bcbc367f38a290c2953cc8192a04e2066482735481884647dc46d7072958510094c36323c8ec7ac50a02e5ae16c2121f6370ff3de19259054826135aecf59bfef48aba4e35037e904353a5c8729de3f1b82cfa0d98fbc95c683eb19577aa87dcdf38dec0e8f4fadd881eb71b203b6305a5d16d9360313c070623fbc85d7e28ee653c543916389cce9c721d52f09f3dc7328feb129fbd0287f38fc077361521536e282608afc6f0376e9e2ac749b58743ea08a86d84d852e97453edbf4e1b11f71fc91818a476a08509057b91647893d8f93561876cac9d79dd99ae57a99c352c7a7c92f1958c45cf3411a8a8ce00095b022b8060fec31eef594199864c8cef435dba3075beb49f012aa6cfdfad4ccc1de284761056d392dd3f534d534c002b384529e209d5f4e6f2f06a78ffb45a7be08fd5ebc78d8f00c4670f27ffe11f122c37ed46582e23682b5ebd6e80d791397949fd2224e32f18a92b828ba69152aab22b64abeadd698d4eb8a5370ddb7981d8075706207217842184dcec7bd39992376631a27aaef9f10dc809d8f4e1c411c4648d33a30cb68a118aec5be045c8bf6ef784e04c531f3318981f1d645890f92694e4e53239347f4fef82a8d6b35c8de1254c027bad504ba83ef87bf7db7ce07473dee808af890e5acfbd56ea0b21fa5d3cae1e364a2d314c799331c19a61eb7ec6f495048560e5c11c086efc88a6411d5be9d30cf279d0dd5c7834c6b665eadc646be52ce073539fcdb18b176e9b89f0acb42233c7c3af4816188e55f73416848b2cc37b1fc61be33e3126689afbc2995c2b4f2caed94f0d5c4ada8c9d1db39564d6316b1a5278f8938a82c15bbd7f2d684cdcf77af213f0b7cd776309ea7dc7446a57baffbb49c498c5a5a6d323306fce253ce874f470a190d57c4df7cd3137a6dbab49f43c4d8f5344e2162093a9b656192a0e15e90b34b8377bced8259d4666f975b0d47fc38632b5209bd06d15bc807ebf8ee8e530f5c021c8f9821b5132fefb8cc6fa9c27a22562918fdcf2a8257700f85b3c936d5bc4667e4ef3357ee86c2c8469282d7e59d7620d59bec033c709c7e1990c51bec413bf12b56d66099f054e580f7df6d54fe82fb72c874cac7e87161d8d798cf0a239f69111e57296ff1a99c3edc25a3484789308253014822e9b95002a440767e2653339ab3558d41c7898ca59d8fba520a38824b3bbdda26249c0d50e04d3f0dc1562e8d0b0d0479e39f46900e91a23c6c5f8a29177a27cb7147e5a3bf2da06919aeb6791d18af4880da38ab767a66fd81a364210738bc059b9fbe38845ebd1e78c76fe9c32ec8981eb75f1aadff05ab0233503716f5bcb34b47cc867a6cdd10e72424b3507246e604e52766242d61b83ad0922e5a28b6fda6c750f64cd2c9e821a344d07b7d42544f408bfe721bd881d6e9a3349850ddb9aea0e26120f19042fec981511c2591f200f5b75a3ea780cb7be9f81dc13b944550fbd538244ee157bee614c09d862c683acdf548cda60e17742b74abefbdd02ba6d953ecb764b089b57424271140a1181c2a0320ef09c23e9dcc1e1373f1a94ddb5c58048f8e04f663896d4c1872d86decdc972814b59a79305d74b17a1d4e4340ff80866a61b9174cd7c3c9bd5c9c7198dd2a31e8fa236175146bae411d41bb31b78b44baef3aeae8c99f820c0715b605ab5be1d5f3aafc12967c882b51f0de852a70985e4b7503ee13bc01d3d296a7fa78f47cea73afaafdded12b733a22a6810b90aeda1e7e970a4bf4549365f8311a1e89cf31506efff081c6ed83fa8aa6c2ea67d76276e196bbe7117e733aea0e632b6792765f32267ae8d9442a39df5640cf065fc9758d83c9a8f944532792330d0f6397d480b994c373c3b999df27eaee89f1e452a94b19ab455fff7b4eb9a61bc1b776b37a4fa5ae5689b530ce4564d628f6ec00a0dde1e60fb210ffb32ddeeaef39cf0d026c3cd6b26a6f71744d2661c5fdcd811f52e0f0cf1b062d05b24922de0487a7aee9b0107a66b5bd2224df31d34b590349444676dbb56a8a4e1c14e39419428e55e6f7da68cc5fcab4144f7e5d6adb8f0d3cf461d660fbd7c68d237a83fcfa599bcfff0104637fc62cc39ed872af84aa0019284f1285e4fa8cdcaba58d6231dd96be3577de2965bb500a9f63691a773b63b0edf72b6cd9b545e3e317c8d1ed27706d9a91acf902f4533544cbdfd4b8b433215b96b693b4e2d16711b3469600bb47c3d6c371221be8a6778e35f59e3e27ba8039b4809a6b61795775b9791226bca459ed126eae285c00344fb9002578e2f1d5569fd14586e4f3b4596459ad03310fe496d9a3054ecb800cee0a47e9556991cd34d968031411a185d312dfd317cae50d22a4f17e2a0f5f2133e8f3fa51ac89c781f50b09f484b1da6f1828ae1b5184fb387a1b2c5ac942622ca71ecae0db8258d2781fde91a65ee1256e5f88f6febc8813fc7435e211d51235d65c4359d8bfc833e7bcce9c212dcd1ee4a4bb55605595b8a8b6f83af55f19a47490054baf9a0a08f42e92e2ed5ede0138a45e7db8ad6fe309d0b4dcbb4df4392e3074db6a1f0048a8e7aed15ed77428991a43d5535061e1e04c747fc01c628ec458ee6b02a6e055c78650b365ad5cbb06f168a508590f40a77769a0764ca4ae42c0462063c823cf557d70f91597d587f0174380564c90015f816edc02029fea6a70530f0bd4bfb5d6b3e77418a82a7624e4edb940d926bc3516183bc986fa4e4a8048724697ade94108cd5185eb95a60e3792d0a16b4d9183e3412afd9729ef2bd319748dd6bf19b0593f2f049e70962e9e3820ba76b82e5ae2d240e90c898a03ae66bdc2b3e7329594fd7ae02303cb234df2b6ac92175f67b22d0003b371b9ee649c10eb4b3784874e4ff72f75b0862accc0a2f0a9584804a453162c29b3826670edc817d72159ca94f601d6987bcd66287ecf23b9cbd5dd3757be22cad5de26c24b0f396ff3d532e10482456f6c97e9c6745d14547dfe0fcf577e548e01f41acd478acfd32bc603f6708debb4952f9f3c424ad4a5d4ff8fb25df82c23baf08d4193d1f71727aefcf03dc41562e681889bbe5411b59d94466d67306c3b39f93ec24be33caca62f4197bcc94aa7e4dc11f9f283f29670204952ced87715026dfcfa0058e6370dfd9516a3832330e82759f91b6e2d3cd808c8b8483013cb057739a129bac1395a5f99491917baba46f74448df9928f8593e307ad9818a502515d40b67ca736b7d47301dfabe75076268d9ae899ab15f2630c2576b55240e1dd50dfcd0b1c67affdf4b4e26a63f25c70daa06e31ab1f80285736e47a7423ca1255884bd43d1428c004ea069173806fd4c6090c1bb7457b9238c9c0962982b5e02cdb87c710c2646f4f25e758839e87ba666d6d2928438b512d5f405b3ac47a1f9f36af8fe724dde3136e01417bc01f9bd46b07270602912c45adc63816930bf4041928afad8f4cff524a50ded4e2d1da1741dfbf738bc54e718b42b5bf12a82a15a8d05180386cd4724880b213c29d441264af6184d223d55162b6099812a8d8402559b0de61808e54de9bb0cd3a5e09fe730f23f8a7fca8236ce71308b1e82ebb8ebe257d197f3fdd3968cf2dd3dc9e1c03a0d185f18b7cd6aa74ba643986e8fe81e801c4da452ce5b2f4dfc5eae60edb15147b28c4daa9c670b25a9108b457a116e9f2e68faa788ca065eb1a5925c824e330c7090f102524a1340cca61149c5b2f13f65cc13021ee0c11ff92ef90bb7823ef891efb03e33798acb3ba425e726c34fc9975c98fbd55b26c769353d6de44948a7516e4ae98f4364e211552a67968f8e61b759826f0757c78f99f3f2d21ffbcee5df55d71248dfe6273c11fdd16a9cf90690dc85339a5879203c6241cae579dec49fbabfd650fc15e6f206db38f15c9464900b1c3e48881e7354e71b3d162767911c32cf2480f42a2309b39771816321c9ff807bfad84187bec34e5ea4a8330867f1e259bc8addef81f53abe79665d547fac01d9683be8afb3a83bed8481bfb7bd14d2c5834c3f425012763d1f506e01273a817b5cb08f4686717f5b1b028fec5bc82ef987f9db8b9189a4fee66ec1b64b80d195197af196bedab2b191d13e95919ce372b0f22aca42ea3288331bcef517955c01cea4a86683fb850340b3c44c229855049980b8af8c2021e97321f44252fa210752c73fb06f17a3768e5ebc1949791ba36f5e7035fdf7cb0a9a4ac41e7e86f39d9cd201b0b25b12fd78b0bb3d1efce4656bd7ccb54004bc532e448e2e37bb1bfad5722cd096c4c41ff363e6409c3004a4f4d8fd88168c8cf7d7888112581624d6de4aa779c1fc59859600b1dbcfd6199d120751c89a1fcde02bd6266915857b013e550054dc6ebfd840f361e3b6e9887aca290f7c59b0ad472bd91946be69c7c67056dd3db5a9473a6ccc8494057ec4830825760e5e0c4cc341152cd1435aea2bb912202102d1afda090a46f3e8e8c26d40bd6cfdbba8f108d139746b14b28ccee87e07b9cb46d9f079d0cd46f343da6b2a1e1eda44a534d3c9e82a4c16d87bf788283f4fe0896de8a43c6a69ad5a097dc193778f82c50e8e1687e0e5256de69456722d849505ca978d6706b5d30c0ecc214059202786df30d84bf0d0eaf03b3744f35d5322481a4c6ec36c89e960372a52776ffcc01f757526749ae650b43f78df85a0eec485082b11c8ddcb48ff9fc31bb0ef2e61b4141c14033896fecbf13ed94d970e98ac52e0706b3f315e4e21e5cbb1b1eee28a70aa43a564c56677004ccaddf698c37e22e62c0861950c59b44ba4dbb9c86d4ddd17f60b1004a0c594865240a718efded719edeef120b8ecb4c6babf8797a447b02ae2d5173d0b223b23f68f720e4cc825c6d3a1d9d6256fb84e57515b7a6b9fb1ddd51e5a859670ac93be322990f712ce3a23d63fde8a492b88c12453cc59d37c1f6699608305bfb636209a887f946295d57af47313fb1b85ece5a1a7dfcd6adb9956a9482cbdfd6875a5edfb9befb01999e50c418cb355a85f6cab61f3be457eb73f5bc7c6f5f2875adfd572d951c940fcb9e459d0862f37f19bd8315de6be8780290cd1ede397b8ffeb94bd294e52033220574a0e7838ae94e4122d9974fb3e2bb4f3f25805243367b2e5bf13b66971c5931a68d551e061d6157829a976a37ee2c78af0ea77340baf74ee20d1d6ee11acdaf5ac2fa3ed11246c0fc2d089bd8daaba4881cb18d2117a8b0527a6d70b8c5006524916e429f11e717133d7c204596e8ebe0fe65b14eea3a94f8e7449db3d997e3f8181d5b6e9a5dd5947227ca7f7c8dedbb672eeebc22cc146d8e40d1a124b55c57abee6da0266981bfe52dfd5b4c730acff44c8f1a4ce53c6c722df9320c16f0da597ff6eb7f82252cbb87e1afc4a2eb790b940dd2041a48f9c33fd146a62e7806f3dd08af72b66d02062ca411dd08517ff55285dad2732404fa005208d43eedc4ac16410e84bd11188cad4bac2ad8a57cfcc83e331a6ce7e78881beb08a19f41d9ed37ef8a06ca3741065b56ad6b7514ae7887819f7ce5467dc8971f718855d69738aa766bc21132535878b7b2301b3985c97685ffb8a3d576bbd41ac4e4530ef690ec6615a7620e7956857128b496d04c92a942803f5b65b6f3e92bf18fe0b746b29bd0f6e4cb28b758c2ce88a943bf9f93ea1305a46c00ac93e9d3699184722099d0653ff69e0e2fdeb958b807f08f91feb74de158a8925b177a09bb26039f2e271cd38a47d403a697d4d5bc3827e1f5789f804b0ffb69aabc8d5ddf86988e5e456b53ba504505aa69314b88a2630ecccdaa7be5729f82445d9e3db5a490831584f942ff931b250145c4311e354a534ab70f93600c6b1cd87586112e210b3a88122370099013ebc838ada7a5965b728848f244347d379eac52beb1e87630e2b7e30fe3369cf784ce312ff6d1eeb602a155683b1147e4ce39ee9cfd93ec777e2329f81566e01486ec9c1c12331adccee5949431fb6b1ebba8541bc5916b923df0ce3e58420fbbae77c03a8b09492b04069708b2800721699ad3453574ad859473750226ba171f96008271d6ccf01900ec4f1e0fd31dc28ecba60028e0c1619c2e566193d21afdc1c6e5bb3c1ccf2dfbca8389e2b01eb71822d7accd81092469add7a24d23fc77b18cbfa31f33625358cced046237e61232033e11f95b732620e2afbc00fca320a655d39449ca1d61fa3e4f50e67d3b02c93e0e2ef4decd6f2c2358f1ecf8a6832fd5012041df61ce6cd2b621f79618d20c2cf1f3be43813f20a871d9b1b92b0a542593fafe67148715617a3accb0b2bae0d28bc36bc7490b98acd2f1153677709b363e25116d4e2d82326212555d9a570d71536fd24c3d4d2aa813d693a07feea22a3a638bb23ed742e624c3572915502c786256c51c7c8408756671eb226095a059743f75aba0ec1c6902e52d4969638a9ed8d8506e4efde9366952cb7929db21f6d5cc7f7eef41f4bbc681b0acfed34d9d32adafe0d2bfadaa357f4439e648e961cf2bba60e10009e238893d31e84ac38083942e7a3ad0b3c98b8b0efb62db75e1676d28d34776fec1caa498483ae275c82fbd1cfa7ccc716d8e21b13ce2495a07c570d0e9bea2cda707860ebe659ff3a483f5a2b27bcb49189e6fba6e1b96c2eff99dc69d9869e544d193b27280c310c67a54893441a4eb05e47d5ca819266bce738d344c32e808056a89ee2ccbccc4be600fae67f70a0df47ccdb08a2da19bf327c9576f9a3a3a878ec7b1343528bc45dc06357219159b97314a22fc4161a88c216af51fab05235ad96fbe6544435c18baa8b51aaf75a321d403ef22201e7e8e1cf36c373b5cf0846050db960c31f43e81fc9fe0ecdcb848395b1b57ca0144894428431fb44d12bd11db7070f732ec205f6e74413e4938a75dbb99d88e1b4293b3ee9a4c26ddf2de81ea77fb71f1caa7597babaf6b0a4136f00dde8bf31bb4f0b2ddd02522c993e1c1539d4b2f372e67e454d75643a3555cb4cd5d19e93358a30b6006299fd1cc97e2088f5f43d171ed70e83e2fba9674694fcbf4a9c1fd84733e713460f261645af4a44d50e6406b626e8868696f27fdf257dcb98026bcefa8f327a1353fcb2714555377bd06aaa15f59ceba3e1092f349471908f1a57b8114f1e2f1ddcf1539c40a405933f5175fa53fc3d8dd06f3e756c22d3d6b6832637e44e2258dd8a4efd8baae139f16ad538259cd6913e5d32d967283ca2b78b4f857d1b36cec8a9dbd512ab96265ab2dbfcc190b155569b590cfd8eeb32513003c687ce925f8a5faf030e60df11f472199e8c726de26ea33c7bfbdb11cd6d79b9e8c6b0dd552c0ac36b9ca4465be0781f0a0a584acb0bce853084c71e568f1df98ea9e538e6bea9dfecbf88ba222e36291afef5fec5e2e2b3ac410764764841748f5b9cf7527cdcc750f935d7e55d7b3bbb80218693e5c1a4d1f34d78a044c486a67e47bfa2f906a7e8fdff04d196ae0cb2d519718e79ef13b5014ad287701e8d192d49cffebea51fafea859fab3e4029a878ce25d12b0ceaa3179663de8d3e37a8b56251d34ff77ba4feffc0636371f51e3b7d5e04f9cf7bfa5a9bf177138eda44712ccb63be9abc95ffb8d03e82bf9885e0eb17d129cefc04b95455bc00a78af3ba03227abbfb56acc47b490055a1bb7dad1a3f69e2a9bf939cc1c4dd7adec934a6d418b05a81c74baade1b16d897bb66ee0cf3b7213d030b078ebe8a6bb1225498f6d0856084a72a146a93445c124b937c3b0da7c4a4944fad5df94d3336f04b8c8fb51091935cd6425606f88879f3dc4ffdd4f498b07a303e23787c0ef5cd65cf38556f0bd925f47ffe38a2ee8b07659c6a7cec5068c41d168f827228799c418d7d263932df0a03e7dfb9447ea0407404985575681e727b77e01d5afaec779a25f0f1571799aa9e03b51fd36f84df40d59ae71405ea11f22734df2503adbc8b9d4b96c7a4cdc099d90ac221e38857aa3c49db4091e0bda37f33c3b2504c1474d25c98f116bf0150f24f886810597b9c346d083a4de33c81b296d7b7c0456a40a1338c6fe515e589a66d5890f8ce9fd9373af1be1e2adbc159ad5e172e50ac946632900ed887211ee85124964f45990b8c3f13993bf806681f882341404d9f50f41c9d7a6c5682a280a1acccf3bc61d57845c1ba71532668c9e49e5b9ad605a2f12e54206ac125ef08bf3a4c657007d87e91f8f1a7876f66a992409965c1055df6262e7a489de3796b884ffa7c5c4aa593fa0f131473d005b530930b6145e677c13f0f579e74d3daeb1e4599af4f4243ed78fc8f9e7982d0d95caf41caabff6ae9b0052b9f63c22e54cb72f33c5a23d303c9e8866f6a34df3736cf26c827f82af832ff90e04aaa5bc2345a0986a4d98481dbbe10581b54597c6f8d5dd0467b0495dbf37b2a5b83eb5939b165edd4e26295ffab646ab28d71043bf239c57f91cb3df43909332a96991050b30f619103238e706bd10ece1aca6c6cc8582829a5c636524c949e3662c08660546d88125ba8973159374755ee6203c8d2e94a776445ea3a3a41f6acedff88c3e3e8b919368ba2373472d8f8317884eb268e764bad13337787a8e79501a989883d165083948dc506311152db55bb20fdc6906a3f0b94caa6eeaaf52d18bc880e42ff4f7c6d95c824da7a41f3bdf402f04856e5f4838a63f9738be1a98e0358b723a5825fbde5835a6f1e8cda9fafe10e9e23af59bcc3e613e50dbee1b8645c015892d75ae8ced78f052a5949f45beec7f56eb05fcf6f898f6f9cf9e597d27a6b97d4a4871c38e54b006b9e712bee8ea4c9e3ba0bbbf79581e63bffaa417c8669c7c061d9b926eb3a1f23a7e4e2d8fa086903316927b6e58909d45707ddc2f5ab066e62c009fb265e5dc1a76910c019fb237a1b622de4186f26a81421ef5ed96cc90d803f1462c5305557138fa00b2f3f176081420520a220d81c57cf0b6a2cce75b0c60d5988aca3da2bcde98be10bcf5d97daa87857f7dff7dbc6b5af7f2fc9b13bcbee9edef4f0bd0f4a39cc855e2483d7bbb69cd516d3193fb3b755bd4a9a568de8378d4140246d0455ccf4c69f884d42102798d3f942dfbdd5c1803a8ba51577854b48798e0b40085c5716544411f12c350c1191fd38b4fef1c7e6c3d5205f67c0223d340789a3de48bf9ea7775906d5174940d80a4d03e990e6bfd4bc6eaa34ee8f65b4ff92db89ee8a384d5f052d619d9f6a721fc0ba8e431676b8f48ee982c464b7dd9bf5f41ebec4bc8ea05071897e9690fb85a5c80a5c11c0d41a3625dd3cbdf3ab95d36beb68c10a8a8ce3726fd32be28da38777e0e7984c11477238e9a5248aa711b6a1a50aa39ec15b07c6ff002a742e17e03a68e91f2e452aa6627b131f63abc3bd63f8faafe48d961dbc90daf28f895d11e50818d16c198b9f2f483994b24887ea69a5c7f0cfa55f527a5494e99d28907838320dd7cb2908ddeef3ae9e266b21512946efbf6ff221e66589e9fe1ada5d034d2f515282d1b0fbfe501f247457cce428e50a73f98a1feb212a5dc00bad0a0f45f78ee8e89ab368ebbceccd879da1f0230878c2bbba3a60642657578499d580dfc779e358da668f81419cbf3303266bc9e1c9bd064d473ff273aa044ced872071a113ba44bf723fd68a5248870252da86b5d4118415f5076e5c5bc2321d5da9cec1bf9bdb1dc4b9c4dd0e354985db9dfe033c1d0b1ff21190a4924bc5c5a0df4ab9ee44d79af027b2e48ceb20672dcc562a387eee06158a43a2b29ecf0c70f13cc92bafbc9ba362cf4a05d62160ad61951e5a2f7cdf4d82504aaf140c613b229eba6ed12388f30929f9ed149900a2411a515e41308fed601673cfb36da5d1bc2a8f198d1e63802e71df7e36e601ceb30198624a100dd1e53eed91c17ea223e5a3173bd3ba492fa34a5c7f472c4e6be43696250f8e563b08c12a34b136f0e2655fe7ad75e17d232f9b743c2d9ffdc532d9bf98a916e5c612032a159e8a077098ca972134813cca631330fe0dc055ab2cc97ca9fd0703185833be88d8e77330d924ec977460d7e813fe8b465c604e676b09ef93ae80ba9c7039641ffdc9b702ac605b78ecabda9b772e3761f6e4a224d886f6dbd932f9ad0362bfaa788029ae6879a4ab20a28412f9d259adc0be07a64597b1f6cc2a42d96de0b5e1c1366ad0bc312ea50f1747457a98136b8f6f5ed934ad220624f9b95cd136efc62babf4d228f48a1a43b08f6aefcf97120331ae111c6672c73cca437b84209d2fed564908acd49eeab30203286327c232239e8967677ecddacabca5993b3b74f0040dad8c73e741c712ae8419c920796508c09c3ad5c13d4acdb92695838e3d29866dc585084fc53dac1003b81c96a1e5d1cf4b48482a9b1133473a56e05c4f984f8b1289ad969d48457ac918d3a8f2c7dc49ac94b813b234a61d2cb35cb6f5f38f9717d01b07e5bba08bf9699be64c02494d500c544271f7d753c8a6cfee6c1d7b03c12e24cd956de9e2bc18fa789a556fd0e89dd607493aeca4e054a76e5db6fcd9c31485bf0e3c3c129f4d08d26a575af2247c8828849766742703d8f37ae8405cdcdca078ee825b91661173a4d3edbee5c9fc0b0f30684596deb36bff07fa321930e581b32c2081100c37f407a2d1ed64c3d2e8cde889b33f93a54b8e07d682b34a7c56432a70f36bee5b36940ce405ee0abf7c2ed708a9d7df509401daed0645e80b69d714446a8acdaa0c4cf7cbaedfecf930c7bca5ce25b9c65245ea5805fc0a66ece325d1f1c9ad4898adb75c9fffff46d88757c82e4132d4e3cd96f25daa3aa7d142567def3c47b5e6fa103aea3ffd751ad150d5f831e7996243c16eeb6e9ff9af8299d979a79bc911e944308b3ac8ff35d3163339d24582d19f463cbc150c41497fa3eac2ed508d98abcdc4ffa9fc9d94a5388c6cebeb57fd0aaaf3da3a748fcf5146e9ddf91a23925843da0b9da079684f5cbacf29cea7caec9df5d7254b0c2de8bef3ba261cf108b9dc86723da035181c03fd11ac4e6493e3a04d56d41c4010299b029ef33f02378296607ad507d09f4e75ff088799de844f191e9972845a8d6744c942db31f883dbb13bb625aac7a2741ea82e4a8ed12d47ee7fe484fc21284620d4822010c680184159f3d84229bdd80816dff0c0c2497db6c077df921e280a7fbd4d25ab6627c8d454a559829d4191238f9db75de4e8cd7e2097453d56285a3ad31b6386a30bf8c369971e4cd121a639143f93c2a6c301a710c7e73aa62ad6d0e9dae2d556a346aaf8ae331d6653b548141f9475d9f3bc6f8c0c31533555cb0059e9afadf21822880479cd71fe04dbaaac6bf16ddcfd678f4755bf47304c1230a829c807e38747b8c749bcafaa28c3572e8894635c90c05fd7463dde2fcb73ef47b0d06154246897d1060348b549f2116e2223455f6d1ccd2522f5f8737a38eb059a693cca31db2f01bd98ea590da0aed84179ea68552b191a23771b87117ffd21637fdad9b269b85f671abcd08da179fb7201cb91ecc60be9df7b52447e4a03a9b4ad17e3c05d845f05ab9c569cbef19302270e84a3ce94346255de8e0de0aa24a65760249b79b2cc810f7937adf690ba551b1c975c8a9a0ecde170aa4eae1558883149cd0b983813664d66d20a7cf9bb68e917867644441527a41220dbb4b06485618e0d13030c2f59ed92a7312c20daa7bb88435aa23fe0fb2968125f4aa7ff4f19a512797c70d7452cacd76ea22ae9785c65f77a02c8c74465c3404c8419c62762e90efa5456fc25b8d290485cb6bbf25165ed9c8235adb08840f0837bd05715b36de4289837a1794d93b4f56e0c7059c6d94fb3a231e6b9d21e89fb569c879c17d9af00f49afb5962f1398d7d0763572c8e26b0c3c9ef1cf5fd5095412c14ad64d8ab21a09435d64aa7d1e4f72b0a5cb45e55e9ae42bd3812bfe3ee140ac5eb6b314f35c7f8de62ec33a3f376bcf12dbc484e810adfaa2674aff58091cb35cf23849ce7d683f54db5b37cdee0057b9b39d7cead954e4c8dee21e34f6c365eb4c46957f4a18b5d4b3bbc041111f36fe2e2d08182b4dc4d4e5ce246b7a0a377b164eb42f3cc9084b3352f5fc3a9f48847cd07d71cf4b7e3c07315b001ee44d8d7b58dbb6653c2e9f68466073fccdc191cc9b776b4db43a732f17a7888dac429484dda595e734165fecd9a4463f6c5039ad9c36bbcae1b2735df6e24ff2d2b41c9deafb7be5f400bcb3a70078dca6c6199493f23f6739ef41b88f90c6b3e0a4623dac27a825c4002a9c2c45ad5969269424123bdebb8193936a7153cb1b9a62ece1e9c8ba13b3956ec8c091e0f83b5787634dc21066ac8ccafb819ff3fe9b0e36ef7cd95afa1ba6f62b5cd52980022af8a08c3e5cb7d36c4c9a0f11fb88ba5634bbf1e0bfbf264d9251fd65e2861aaa4ead28007150b192d7d10ffaeeecf27074676de2f894c82f86f1693c6256d3ebbaca5fe9b989a8e595dfa67dbe71a00999683aaf3ac9a945a5823e6d73879cb62404d95b00a3cf485f6bbd03e733da597cb87fe505db5c625e26b061692160cc6872bbcc50bb29b44442037e8b39392f029e86ed367b57a371ac89a59757abd2817ce4853e225a6e5a94c4febbec3ed73e54bc58151a7bc4848ed0655d70202109231e68545f48c21b22191060dfddf58fb1ee1ca70ddda446913f79f4507b416dfe6a027a4486937c11153ede83573cf50b3144971010b5eeac9b579214a60caad36cf4fb1029b3639d7a36779b2e5ff4a1288a9cb4ea9257443b1cd8fbe224f58b63252eb216460de7cd46414b9250acdc8698e60955607288056c0413343ea8bf4eef58fd1f10ddaebfca233dff6d2f46447e5ad80ab23b1f253266ff28169d490e0b80e1aa07abe52987ecf4e67efb52688c7a62da3316e6d2f97d930f5dc21e2f3a53ecd4aa538ea72c3eb51decbd05a7a9ef08216f0e9eb52e6b4e8856c29acd9f51e6be5dc1fc4188c7ef57c8de4ff8e6501ef85830fd3ada5933a6fb3e5da45afbd3afb49716c421f9568cc6f65c1661ab5442579f40003c406d0114be1d92ae20450f7cbf2e14ed2ecf10d85df43b165c034802ffd8107d99f779a80d322b81899dffb22b09b5781cd900d341242e62773daf41193411876aeafa5d7d28210b7518024e993b43f168eaaae5d5a64deec3e86d311ca778b3e97017bea6aac1493c6af4f06274c957268e48767aa2e1de8ba397af6b6654f07f40f6784688c126fdf7c702dc67920afb9508798190ccf2e1bdd1bca2e3d9dfabdd61e05be7ba10c574b02a719402f69456d885653f2977ac994ca12cfe54db7183ceeff3dc5ece791902980b2b725355523af6a18d9dede785ed7d7bd5699769010d402a55e69791961ead2aef58d5f18d3bf6255bad346c109b294074a9017d2f4d2e04b1758934222f307bec536710138a33de7b5a1566c467be2c557179923b5ecedb2bdffefa16f2e578a456b95fd1f6ae2484a99c2c635f3ab6673f491c3d9cf694f65341074d45a82ac381ed2381778cf2e2a838951c5b479dc50abfbaa01f50ab648b9d47465fd1fc9cc9ddf8fd29f9cda12a5303115388ffd9cc973d7a17c5852dc1dd6a64fd1e809dbae06f10dd14cdf9a3748b4c3a7d2557483e896cb0810d9455f4534a4bb1634b3c5220f49a1cffb117ff6ed4e68e83852c730ec1ea527abaf72e10eaf37bb5a9bbe24785bc9d304644fb1a71d8a937269f2131e367a54818fe12fc38f96f0b0b5b4bf003bc9584757377dda2d94a55e31bda404898cd67c33459f2560d3e12ea3cfe81fee116c11d5a95ba557e34892c14fba17ec4280d465d6038f3391a913f639bf8434eada3d3102cf4e8d4a963fc2a3926de40d60b91a916612c50fa60504fbf2627c5c10f450d3b332152c6c92106c932e4fc04a2fd4d11593f858dd95375959410b0a83bd3d5978340cdc778e19e74adf0ac9c8afe1d81345cd724c1b15bed0bb83ddf609072e57f51be5651df22194458c6949e19dab2c5b31d1a73d35289505b82fa1e8b5828a4a8b4cb24df6dcda46abbd150c1481e4b16c133a10516dda9a3846ded6972d3de598326c53245dee1b4d4e9f0e8e49ee3122cc0ad62d64373505ce833ff3b2eefb82ff2dc066fe22ec80d26d0765c3ff3b7cd3e036d5823bb91a614f8572b8d5ee63335d1118d2abcb9c52eae0f07eeab963e1d3ff9f21e343272d603ab1983b8772c96c8592cf7a47be64aa1dc57161a02d3f81353aa795ab731b9c68bb2281ca0332795098fa10708f216d91caf22550e1d2715073bd9fdd50df6f41dc4be4b3c6e126639f963f9dbe862058907722d104362822dec1b35b3326945b72a11b85c12143388496f8bd9af871be750f9f780b70a1f28d1fb2951308b470d0544d1748d723471c19f464f6819c6c073e430c4507d3a5e96265cd8f0e058bbc2bb1f5fa3f41392f8973fa1b68ca362a9442be5ae15ab178e3542e359e85b124b52789b9bf06324444e9edaf36cae28141d99a22c432f809b349e549447a9a38eeea59cf9d2bc1b89a007025974626a71f64235186a4ffc6b73c5ba14f5e9ac83ce5bc4c546e7b5cd1f774fe877e8d2f18aca236121b318d7784ebaaef5ed6bbe66f118cdad4116cf296f3025ad6b52559e62d83e341e1e8d4157c5dd064276e5878b5221a17154c9f73ef763dcacd973c1c2fe4708d19e40ffd7e2f40d863629523b9d945ff408a9ec0d16e0f78ac98731fd40cf659dbe61124b3094f170653bf2ff6db4e1eb74d7222f9dc259f5d899da48e23561b56ad74cee1db70e842d8b03883545bb7ba9d28491461ff3fce8b366a08811d2df3d5609aaf7c80afbb18c20bcb294fb34c366ab01f1d5cd67e74b36991ae7f06e5dd776f72b7eb82ff8744064080270032c9f3fcc0841df5beb0b0476ef0fcff81019a5a61ef7d440f7085d4b482415c553a5c716aa85f6fa6872bed5f037fe4e5f8cc2c0e363ff0dfe5341e17b5bd12a6e63dbc44f34c329218c965e047eb14a520bdf32d3013e86d9a48614ba5cf5a70db7f61b65b52bd86356885edb6bd6c2557c7d409f908e12ab90d021d5b3c31652c4fc910bafc3a8104bf5884b98212c2cba99ba5b927ffbc2b9ff83379f4379843e53e93d40177ef564af51d8d86fc5f50e41de5188cdd1fd49c0e78b77402c81313d45acf25e3dacaa17958d39afea4394eb4fcc99ead9202f8ab82bf0c221f6edc125b75f82567fb1b55a32cb139d6616f8c737326d30b28211cee6b72d616005169786dbd81fb6965f04a6c86329853a160d18988089f6c880ae98945f624e6bcaaeec37f8aa4dbd5950308c3c9a708dbc741bba1a780be85240f797d3c23f538854cc5e551125080d12af52831e390114917e3a886e3ce7a091049646a2cda02060f67814b38760d38ea8aecafbda7ae8bee9c0ff0af553d1c1ea6f7c8c77ef7bd5fff83cb0a2d4c079d44e57c83a2a9a3d7cfcada887f2cd6657af55a09492ec97ea7c908523130580c7e7ff160ba22137900d34935eadbc7edb408bf70ae23e2f06a45d30f645bdf9f8d46db669b6dcab07c7c04d158019ec345bfe81288eedb494656b7220885087693879a2f1a76ef1daccb9850334b86d0238d3372e0179ed80701f065f7d26f9ff1dbe02dbc856843a817049549f024aa21e2b1ad49537a12b370b3d67f46f3990a1bd08dadd5571e63dac2e549f2a7f5e7552b8d4436389f78fcd4d051d327bc6f8f5c79d52e4891855a8f342d69851ed83906822b472755ea1813446ca0a27009acfbafc2ea8a640cc2d07479bb89690ddac8b086bba95af1395d77482dd7dd889b2011a486291a372d118f0ab9d577c18ea8b9459618f77f02041e8b4a141195f0a6d0cc96294a6b79e3dceaf1a64a62d4b0cb522478bb93d4f6724d7ff6aa4cce87cf056f625e00982a9f35aa04f0821cf4b622a80db331b9f16decfa16ad511baea08a5894ed86dae0186add7c406e1bb34576397dfd5d73903411c5329c784c47bb2b0f8ada165504fb0873bd7483a854a9ceea566bf0fdfa4729372f13d83fa3dee650afd87204061c746d8e61e199a967b5fbd32a09e4c09017af9381102462c6d35be91d28868fb08cd39b9bdfc8f2ce8c68f9a15e884afdba17eb629dd037f535148a3d09c87b919516743eb668c980cbb79963a191a995baba587f5403f88653a932aee1a613ee1274f7e0781f4ce33bbf12767cd048f726a4a1f1fd576b1102ca37ae5826ea699ddb71f8a24a0fbf8d8eb614e8d9318cd64d9744637db9a87abdb247ca12ba20aad6f91cc6c6fc94691b4f693d8eeb311923c666899626ead213312637ddaff803ff9c5c388d721f6cc5b772306ca90afc459bfb589fced508073de6e723cf96360382ed6fe9ea69bea14b1785b4b5bf6bf69fb16090c4d4e36f31ad860c9ab4afb7dbb8e59591cf2a44bd7f67ed52794c0f97e3d4e336ca0a4aa60af2c6a8819ee6297ae930887a290aa9170daa01fa540f292608654e5b242583cf9edc6d91aaee93d6694c9977ddb48d0896490f2773d12f9543a05b3ca670d07d1be1ea4084d906a6bb9d5e88401980b337b4274784fec9b4e5407b1cc5c7c6287bd8f7c7027902887ebacff9f3823569386ad4478f8935f36dc13e0e1adb585ba9027dd8e31dc7eda96de5ab5bf176d4f3f8b330af2f705cc1a4b604d2b06ccf6b52daadbb430981a2941d5ca2469e13f3f2ab0ac96210826e8206e56a2487d80f480de02edbe77de68726e91a53eb549606ed506fc61a3bbe9158e2c97997452a738209ad3ca83d9fd247923242f328b0f76710e5364eb115c72923d1a76116855d73d9a1175f6acbb104f58279f18412bc9f87bf8cbaa118aface384052eb89585d3c18822249ef159877566f45b3cf882406e27d97018b52fb9a9611cc0ebe1a477e63a7d65f1b5b3d4ec969fc7a9840b7b726f6787bb253b96c896813dc3564f04a35f858fd77faa91f9172ef05310b2203a695c51ca85085f78168c85fa429fcf60615f4f6494ef916f41f2434d6ffa183d5e04137a2a69c6d8da9e5fe8bf433f24ebab08f7132f8855509f84fc52f995e918511216debb3fccb49a93749c0b53c81b2ab2bf1d432250154a0659617ec34534d771a46daaee7777c0b1487709b9a47f1a7b706208f32f7ae6e3502eecc510f90170f50a09ec896ebed4ad32655d4135b599a8ed06fa5d254d92198361ff29017f1dc77565a96e12792a7a45205152a8d1dad25326574c527e1885a66383f1c3bc2fd125c891ad0f16e00d2af26b7dfc8864ced0cc059ab7f58a3dac0de5bf45d3324354d7d85b21c58d2982d6c94aca1adf666d433c4199abc9d9e1cc99c3209360de2c585e56874922c50389c16dabb328d68ca5a637f3e7f83772c78dc806403dcb15060e220c708b32cb4ecf0d745926eeb7d5097112034ca7079a20aaacadf46068b02a195526f22ca13c5686f5822d90d50bd95f9987d3352f991717e42bfd2e77e87603af932908dc14cde9f62af84c86fcfb3b031fd295c4d5be678a0d3324384a81386b2d5b01d6b08fe3d2a26c32957c5800214eb88eb75fa60624fc30820448e70e34b872401d9e1b377168c6d6ac42418a548904a7705af2df92d191acd5e16755e692896b6ae7aa485abeef88e1d9d4bf05c218ed7def7c4a413bdd79d8ec6c9c984ef35c505eebad0c23786e1f5a7ace565c82202ab013557499c10b3aefa6d46b412c3fa311f7b44593038c9db1c15bd5edb5c79662feec0829126fe86e0ece437432ffe39d90965f10b7358651a28b875ff94f8d4a5ca6112e93c544a6650d9b4b99da17d3d4afaaf7436d6b2c237a93db249a0377d11eff980ec065bc3fea2e377d0affbb0475d37bff077794597711018926d6f1a39df0b97b16aaafffd2608a49e9f9b4eccfa889d5a75df4e0f23f092039ba381bf055ee95c76a249ec99e739504c76b77c1423e3eb158bb0f3295edfb56d38407d7d647835e3a1718c3e6523a33559b0746f16d3a5769a66efac9fa1ec64c1d92e22ddb03ee05ddf2b994603ba658b855b873d31ee9143761ab988314b70201092a47be8c7e475e3eb5313d21668e12f12c905cf996ea9b53179eb74f8f49f0ddd4436b598960970b95506d6a52958b3223c8510358f0cb43f588ae9d3f4171c0dba90f929b818500baad3f0405e6668b5000137677be99065930f42eb43b5d3a4f6c8b72788ce8a2f25524d5bcb5fa80d6e2d2e8ab179ad32da808be9098efac993a4c46265d0e00d41e9e220a0faeb64166652bd0f98073626e5471959cefe75b14b42f441809aedb6ba0c14accb0deca86c8cb8d443c742a4b06f4d1039dc2d38333331d80e75ac97000bdb42723912107d8048bec84a7277bb485f1a86ec4ab14016a9db3f367e25458211ade60ccda09668d04b04885e042f93c70cb8e088936a18d5276dcd1dab00beaf911c49224059e9afdcfc679e169a5217804090ada60a1d30cb718dec82688935cd8c1bb1f4074555a2d12856cc60594e68b8dbe234a5667e54eb233089cc7acbd6095dbc0a028b50b7770792cf7290826564ca2406764da0b7abcb610cc398cae878429f84e2398995c257b44b24f04c784334976ccc0214e63d868f20c06d1f27b91a32456b7f81205c0245ba716ed066796e2f8e4a11a066e779f944a00a79642a7c7eb8adedc56587d0f2c248b4ff4404fb615b0ec00259d24138e0850b2866d7f6682c4859476c66b8008af17a685317e0dceecfbc8e9d780766c05b6ad2b05d391de9f0ba2cef7d17d23e0b2138c2703700f9fc0184445739c8b1c27a033f7641b605bff41cdc2c5a3d0194c200c163f9078316b14dcaefcd8566b200b1b1a573eb2d3b478b9c1cdbe153957364957a6826ebcfc8ff5e504c443ab5b5fc93f1f810428278a7d3c1b2019e1660289f24cc8eef628df27086a48b034a709abb3e419563ba41ca17b086683726a4c028bdbbdbe0e2777ea02bd0c147b81981a4d19c30c6d8e18529445441453e3f0eff00f2d8277a3853ca9362c0723ea537c67269ddc4e4ecc84bbb92ac763b75c059d483d776ba3252707a3f45078b751e7a944c052133340378707eee124e2be411ed0e0002b7aa2feefc404d8cb795e83b7e83b18fad995e655c0d394e4060a393d14bb76c640e68bed81f00af6b2c3f6135b403dff7b07c38f54ffc63cc344f0b1193c76ec3235ddd033da84c73e5b018f36f51266c3c22463f8a028083cd18c7662d22ef50dff7f7321fc462a3b5c611667b51e820ee7d7cfe4430575ad0a5f098b2e1420a24c621bb7e3c61e597df64eb51b8534e7c0a91ffc8d8134791b55d3b44c54abb97c0a84fe9f24c02c24a88e8d0abf4cd0ef2c287dab8fa373be29ec8b63bb77a9741cc5ba0058c63c63faa426f613e1fe97b74765cf31f42cd81e083c2bbdc989deb5a5b6a4e06ccfaa2baa02d38efd4c8bc562f26dd3ded7165499b81c01f141f6bc52a39acb293f375d95dcc2eb50c39aed99b3878b64de09e270776fb2382e07edcd004b0a7bfab402070f59bd5f6330b5b1c54b5326cb7dc35f8f58c6c0580791bef8176e8a05b1f8a3806bbdfe707ca48a9fcf870e90df3b5727f0c0edb9dad0d071052217bf496147091c54c87f92ff567e24563b4b1321fd3f0833de842e154139daf149b715cce78814237df7c5ddfe4db0548f248308a5d5d9845e69919db7d21ad005a059ed704956bb2bc7894fbe90d29314aef5c9e7c26f9ca48783d9526aa33b33a3dbd3f7554229f40e2db675dd77139c2fde3818babf301b451176dd462a199d0f73b966802414e63201949743bd91d243b5d54115a3e85aebd48b006973937e4f6d6abcd3423a8091ba3ee20db6f0464a740322658c7ebf41b8c8398830fd9280512e21d0b17ea0cc4f0669f385244f02b85e6d7678d2e030fd13068daf6f7ffd87386b8017e44c66050b795fba1a939f2e9faecd93d37301940898e8722c6c4ac6468f917cd5eeb2ccee852e76154999c70158e6374c38a98383fd5c1344aeeacaced2bce9da1e7c1a378817e1ac9824e404fe31225c47cda6e8c5bcd39346c8acf1127c692f38426055391885d8252e28c6c2be79eb1e8475ff6c09078a2f70ed19e9dae94e0180be07774b494c6ba7adb5133290f423aa42e62ed7b99bfa402c8c2e798820ed94c84d0cd0435ca7d17395251609865ab852bc77152116dd4f99173708568397a8649dee474a0ae1189a9a2c0731e9ceac18c2b0a85cfd01995023826457f5bd46dd1a7dc7a608c03e682c77ee33db655642927e1e1b2f063be86c000902cc1048e954fbf2dddc634020cd2d9aaca222862221e9516359de0787b88bc291c7eb3231195a85af52ec284743fa79bb4c1a7382701a5031b4a97af2b78c4849610637d4fdd0f4259374684caf848068967d821744a3b1ff2d55b2030dfdadf332fbaf39346aee00c937d253cd2e6486c5a27ba7a2387faa5393dedc423031b26cdca02eaebc44055ce5879954ffe8e5d3fdff960eb2b338b85c4a378f32a4638cbff6ce25124913d6e940881fe5afbcbabdb7a54ce18145f0ec9932ef1fbd25359e9c15b45ddfb601b3fd7805b8192db2dea6ba05a5143781063559e5338fb3a57081ec63574c35e5a5f0dc00b76fd5ecd470bf5fba8a1fcf06e8f557d4263975089ac096c2891ef0d875cac994368638fcf6eecd449878bc31c7a8fc3ba1102bbb340199bc4b32e2a4d76359b69c0e8234138e8f2627500d84df7b751f1997919feb0345d79e228de26da4fbb70b19cc0ecee65d30b05b58fea1e5cadcc602b930d43d4933923bb95c138e03fd2a0f2aaebe01470b07e323dd22338c45453566c503c84a43490a2b9ce21e42b5557f05c8c7ef5dd57d3860c0f7288107c3358cf1c67f61f23d954076cc0e55c36b9933871547583f784cd54a1c9297dba22c67cfeb1787d666d7f8f1fcd7e7a59b802be8c9b8a98b52b5c360e253f719d3fb57c4c130c3c9c91934ea17d0851b65ed877eee2e7e658b0f290611976deaf20ba9169092787fab22960b757146792fd35a746709764985e3758fdfc5ddadbec5973b985e4ed3cbcc28459cb415305f800059a407294ef9a0869c421a3565539e748c920f17ad742715c8e1892992ef98c724044cfa7b91a703b6ce197c7b0c6ab8f7480725055424eb0190d7943ea8ad544d692e0e51982353250ab884cd42f7b8e6d616b0a3ce58b14a63281fabf2c4cbbe1723e6bb4b759ac3497e7ee4cfc97d38f326b7403f1df054b7602ed9b96bc0a53c3218e9a2c34a9e219d9810b7ce1e86c80bbc3472e7446d35547e5c779ecba8863250e57d0d6f1b1acdee7b90facc786144f759eabe45855b54d1c1ae83604ee25aa402c073c852708ae7617c803eb56de148760c731a9d5d084b118a4e8084d7dcbdcd8c9c87b770c759dcb625b896e6e26b2fa630b5f73b8a8ce9e3423ed2945a1a0ddcd56ab8062d4391a71f74c504e504f3c558f911f0bd0c93e8f13887cbb1b0492323f2a8724d1fbec8b00cf3178b7544e8e88fd422d75f632ddd892b2f34a052f456f52ead0ce3b19f0de8f238ed9c28096cd8d169e0f0d0699ba38d4a3d119060ece4188525302b674c6730e4ee54b052e43edd565a6199e77ab9043df8dc47b544fe0a71eda96849e3dbf2435594b3babe31f03d8306ac5c73b33238a2287953e2841ff7a5c005199716132fa29a8b396cb4d3d0548d64b355462560138ff123fd7fba8a22fe7e30c74ef33f2dcc78723be45d6f09b97f3a1602a3869674971c1fd173987be5f0fe96e7611c0069b06bb8f28a7ad6da5bbc95d8ddfbdbdb34f11b0d5ceb234582657d82a92c223d0e962636b83947b5cf14d10c0515f0928bda6bdca8c44ab8ff87154fe84f8a263b045f7906fabf98f838a0240a4061350b7696f7f8d40db777ba3e718c207d7d314343ccc593662c394798fbb54d84a204563c43d595f69486992c8c1ec2903e7601fb246e3f73f9315188d20272f58050192d722fb663906918fd202e78f9a4710d7acb2e4a7a33f40160d0dec1f1fbf2da66e2933b5496cd4bbae2c0a020d90bee3548eade71f237403f7a26165c3bc34ed0a720f046d6049b3276372aaf7f75edf36b2f94e2caac6a0508dd9fe61db64f0902e5447c1824462ef01644c584f3c962ee239fd1f0fa4454fe8294bf1e7ef0d84995353b1eaa4e6d65aa395826397039e04bf9c1330ad01a02146d52ab71c7ccb9d61e5ca8b72e49dda0ec5d76db78a0e89b77dc00812570cbce2998d6ebe70e0f361ccebcd1cef8df631254adf28ba52201577bc2e47f9eac6c8fde9fdfc5bace66be3653fed89f20cb5dc26362a90ce5256ad1c6e8a4a0ce9578a8a8bcbfdae2796bfbac4f2bc3d4cca9fafbeb7ea277984c4d0c19100405ae53edf5cd5115397bc575fd95fe6197027af86c0580b8044d88717d5d7331bd1fd988ec2a5bca728a5df2e65f57b9537b842a9c18b734f8d4e5741678a17d710ca3b939d3c666d6a11fd0b89aef9ab00b0f1f82ddaede21dceb38b096761491a3d9f2a0a965cb40ffab266c2acfb72e846b54c10524e51b2e55f7ce7e58b1a4b76cd26a6788e60962b7bf59b9fc69fd7b739a16598af4ac85310aee4fb41f090c05a07b137de22f1b2081f7d25aae7a04347cc22a1c629a240b9e8e9cef6ec621b325ec309dad4506d0a8e78f9869ee28739c366d467c84f9085fd66d44c629ef329490023de187b63b8ddf4843121f07b2f53eb9bc12b7e68707e90c8472a027907849c1fee82450d05b950b4a45dfc4b478962660510b3ce3fbd97b7223f404a536233712e61651586e8a94295d171113320815ba859be66f34640ac3cce8952967f6dff00fbc3cb6d726f35363f5ee1d50a8be8fb373c6376971b8f3a7db06fb4642eb75d01122d4baeb5ee6f683f1511013951fcd7afebe7e00746968b47fb0c1ddaeeb29922bf9bcaf2310259d1686a38b2808583b1bd10e2c00624157d8a794cb2c6627899e3d72ef59abac5413db20f8fed7701f5d9edc3420700c737f9ef7924b8c13ff7c9bb72cbf314a7940bf7dcdf56e85a040cd72fdc09a64587c13194c155f2715e6e2521c54ad1aa24e5f45121e26d113c0bc22f993ea716416626423379151ab07625b4ca84838386ab1fe74497f7e853cc28cb49912805757cf4b42bdc0cfaedbd12ffeff4490f04f5239d78231f38ec127bdd43a74f2d1ce113a45bf9266bda36383e46caf5cbe878daaa099ea129e2598549ef84fa7710426b91bd19ae06a189d660bd742456c83ce2ef7d331d92122cd93eb04cf6359d6e9817c3c1927748305f7492682b59e3185f1e6e4943af6c6965fe32a886e286a2ceaf067706326b597b7dae389a32feb6dc128ef0b84f01009d9ea3a29f20ee43def2bf77ef72b4bbc83cf21472d7a1bfde05e00063cd787b27125b83164a39089ef5992641358188256b8730a320adad642d980546b1af7d54383e04e28f214e5a283c54f654a6e6f7e8f528bf2a8e3368ca7b9650aa337fa7518c091b2c3ffc866cd52deee5128a4527b2913ff08b58093a78f650eb4e6f4c0cfe40636c26d51a1f5cec73da2f313088befa5eacaaa7e071b6eaa860227ae7587a1f4d1c65f4ebe741e14a12615d941af392f321e578da35ce1dee41906d2c15ee78d7b47755789a34bc062e6a67cbfd1d0480fb06bce0476ea07e862d17efba3fb05bb87b739ea7a2a339bbe5e48b7d406473b9f170d00a903c412cd5d11f1a6e9cd5e3bdc3d406a0910f38b957b35950a4b9d2fe16536f4af2a32a705a7630e8b266b3dcc9ce94f969cf45ecdc6a599f32f780a6c1328908992157b80f4f3cc5c8a3a269c2c2ed45ec0740a67716df35d005f5c890a47ca7c92ac4ca2237ec428ae8008950bed67e8216946bed53e3524b242341f5985e919590855b09410dcda3ccb5532f91e4b2ec8751e7ef597e7a526afd9cd8d6a6822e5a9838eed5ad43732fbac25f27c67fad79c18139fcb0fd77588e70f38f6e52813378c6bfe54aceef7c5fbff997ec29ad3a271f68bde5c372ed8f135a6b9f89c1ab8546a077d6021eac4846585e0a6462ead82d18f0cb2e3db8b2e39f255933a24dfb00bc3414a5300fd9cb12e440e6c97939210912f63d25e30a7c50a4c82b138b0bb5061473c14f45d2e0cdf2a2f18cc09dbe6f871c38150f5b95fc0459e722eb27ab03e31e3f474401d2cc107066ce8d4ae6165fd493b023120688abadcd506d257ca60be906fd57e1926c227b6fa549c0cec94fbe95fcabb5af2890ea7604e711d7be91bd56780fdca5fdc302c226fabcaf3bd67861999f8933dbc677ee79693081957f2bec0b69f1a0ce9d09aae6de829fa23585e744107fcdab0c2113e292227c0fac314e35d58e12f4b4916553a7053fa875f417085551f3cbdc80950c9da3e4a209b70f33698f6f264d6b210f7c5b5e7b11a771783c3a62e65670a4ef3cec596ab794b7bcec982200e0cc15a36f010eef6eb635ae1debc5ec59c7e4a1f899e32d364024939925b06dd406211db63658194f9818f743dcd5497eb73c6d1ffbf4dac98ee2d24a3eae9648d9aab2d9cacbc7da2c01a60ed7f53d519a1ced133f5ae1c019230fbf9c0320cc8b80e0802698f14629608fda828c2d5d7a889f7d15b0c7ec1cfcc3836e3c67f3392384dbe0ca5dc5c4d193db59803310de070c43011f2bf8941460cde6c7d2836daee4de4ed2e6630c4f9d2d5513f46a96e4ffaa058d01fa2903b3b5abf9042a986bee1ff4eba9f4e172c322437826bbe89b906533f73e89280a0667a9445d8afaf486ee295983d489fdcfd7d67edfa727d261c9c9750b14bdf54ee04391b6645412b5c8d8b756d5d32577e66a19512356cf1be5bbd60c7e0ae280c80e856c4366d0d6622c9d3e82377098e6535a41697af2dfc244b843dcd88dd7ca8984aa41d058dde8c740a99bc327a3286ec433071b92db72622f38e90a1906816e049e132a56bea5d55f757aca71126a904790f18eb0cef89e340ef68aeefe746dc54d8c29d9d8b735e268497cc90e320622f11b6f463f55cd168c09a3577d2e5f8accff6bb99cdb9f6e9172a6ea415652f86911364f3aacd530d6facd861230d7ebedb6447c2254cd9b1d151ab3dcb0f67d332e2d7d7bd265cc19425085c85ff3e00e06d8070791007519a3e56c3a95d4fbe921c5e4d3b6b5152482faeefa13bbe5a92fd0b3e25ac1e9cbbe247c5f369f5a3f07ac9e5f6229123559af5c8d4e540d8d1e025463b5c03128ad863b90d818ea6ff3f9670f548ba1070fbcd12e1e5429cfbc622783d10e49b1dd7a2c0d8cf162dae95b9674d42281d704a463c98aaca557438cbf83d013785cd1bf73a7278fcdd390015a7a2e234cbfdd7d577e0edd2b73998d3a98d809a44f0bff77b4542908bf23c091bcb4b07fc62398323823d9b734d468c15099e96355aec46a566468d33789ea4a799ba3de1df20c79d61ce8e7172fc004f1d0c066040ea9649eff576539bc0eb49558554257ad1997816d3f746ef16e25d77ce6883e14c77aa334992fb150f1d936e5f11efebcee2bdd303660084142ec5e1e5591f47e7d145148581336b978529214dde6537891c056955265d380c5e8b39f46a3b550566088862d2d7c316295e6cececccdada57ba2d88261baa62452fc19d85411a21da07362d9693a682a0b5529179ec0ede144d5e98f7f3f51d9907de68b58b7f56d29939232859e8d7625cdb81568249a59f6626870f27e8dd7bf5a2be87aa9482592636d8154899d43d1e24ea5daaa6b82d8a303330238893a9ad9e42a98912a37215c5546bb8d3a6f84334b081f539c4a3e0b7a26023e8d08ae400a8e4c7a4a258f6595c60171b90f0c4947a2c6bc7b6f853ce04750b23def262b576e670fb8e23bef8915830493edffc820445c69ff3c08b928ec1b5c46c0e92a5301e468b2c86d45c9fb53a7778fa6b3011c028de7e9f81c14f46fb6160911a02c8326f56a3402ecde77cf35ffca7ae123e66e9102981a205e59d52b1a57c77ce0cecd2b90f04efc17d7661a1cbab7911be7a44eb309214041a27987528f3a8ab6e629591ddaa1604682c4ace74eeb410ed37b7e2f8bf18231010f2665947082877e61bac3e3febf88b1bf96db8f2fd9fb87d694c7e70b3b2278cb881aadc0fea41f0dbdaeeca2ed9d2f0f9864b94be6e0d91906198e98886e82ac58390f3387d1ac602c0ad41d51d05f21516be6b7d447af777a2464a00d959561a52203554ec529555c9d8e1b10a70f70d0cd2d9812de5c1aba6c0aac65685e4be9c9f1962a72aebe0bab29ffe125b9932e45714adc452df6cc2d7647d874e4551ea8c11e7d401c9df8e35cb5c8082862a58ade84b534a5387ce1383f7df351082ba3e8738c3f846b88209824a09353bb2bcd457988632d1c3c458da6d7669438e555ea1656265d77867afb8179e48e577c9977fa3ddeb3bc0840230dbf3a5f2557a354fca0a55c3d2a789c5be30fed3a7429200848c25ec0104ec732ed15656651e398cdf9297145f275f57caecf2d3052f9c060642e426ac3b11b89833fbc7434fdd3b4153d2feddb9748bef0ca17917087057073c6c3ac6df94a3f66859d8a82cf7afb41b5c31bc35a51dd29a6edec9cc816ab0fd18082336e0fe3423ecc246d7c73d3ea172251bb55a3bb9e6f1bd44af18b2d04d0788ac130226d6cb5599247df8989fd01077c172976a5001d1ee2a99c7a661968846b45da4d2b296aacb4a007024cc27732e0db108fe1d9cf5a95664769d3547bcea4b0d07be1dfdae17594990d60ad44b090435b2f82ffe18e1ba5ce35d00d77eb0679c3fe80531e429ce56ae8690e5124f84a79db73cda68b4e61405a1dc0fc50d83ed07354b3e6fad775c1f03d721cb51293dc39d1960644d13ecdc59d21c77dda5a0ec0797b1e2579567d9c8833a66f0d79f357a4e98873baee0d39d3b159529df8cef8e248ac535dcbc640275e28926e14ca6e823c5281cff67538a03d17bcb66a8d3f7d7125892af58801316fd79a97f1ef2d0c710f73a7f5f9f6cb572dd3ab966317ea5028e910681a02d9ca679db65003f693f7c9da2e9262a5a2b4141442876fdf6b4d817975a4538c809d898926ed2620c77d9385e531bf9615e7d6f578646dd05dcb687e01e7438e772dea220f4de7902c383e734b4c95ef6bf5e2ef4d1525501ed79fad2b1364c7243f07c2c424cda872b71f39d9e6473f39f36c8d7da0010f3567b73979fe43955af9b6a6b1fa744139ffb5e0f49a26ee8754e5bdf56e99acd9b5e2e01632e028739c9ef0128432ca5850a2cd4dff21d7f7bd17ac799abc785b7c88fff0d09672351971682b5a97122c6086b3526f4e14b7a0837aa8237d9ee491c1dedc627ed898bca3a7b3f98ef9a5c92fa44b7f17362b0c8f5e37029f596d84cba981dc20145138db82e3120c40453bde7b1be836bbbaf0fad6634ed8b5650f62e3e7883a0230411bc555dbff0fbd67ae1945a0aabb0b6599c4f3e3193473d34a3d15acc0dd96aeeea6a350a46b81a9330627c710c7dbeba0330b4675b06d8e15a31a0b9024d052fbe1aa2dcb0b546f4077ea2a66a56c96bb7592319f2922c3f01db18972ddcba7a4b083af851f5f4345e536633fd000750d97a8434ef4adc51a23783e61f9a27b7651a82f4b3d4edd5ca54ec314c89f14944849325cd1d1f93dab8125357a43845389267cacd20df7f90759d7093eaeb2fc2c8955601c4745d2a3c6686f10dcdcbc6eeda972492ca705a4f9fa7df56c263e1118306e86dc9571d181b1f6a91f7ae1bf2c1e5e05a7715bfba32a88ad3544d7dce0fb81ddb5ddf08adf73c4787f6109bc6cd55803c8721583c132d96103c330e5fc11955c0eefce7a806bbb00a6774d895a3a89cf2676fddb3f08551b06e11d9bfd5e1ea9958355cd3c0493f0bd135ff346a26ee3fcf7db2cc85ad97a92f2791e4a7948855013b399919bd17bb807a1ba5c25ae5383bf864ca2be772e7d2cc2becccbd40463fbd6120ec97067d017feec1fb29cb698d340d03186334af9f4c1f612e910d47704facc53cd7814530abcc0b9b550f7af579829d2a597c4252ccc1af165b6dcaa6465eda58a37b41e3ac331a3474cfa667dd0b8539070749d1b0074b92877ecfe46dcf52a7b60aa53b62b4aac594b47d2445e81afaac61c378c3e3fda5d5d7f37f10654bdf7dc4062ca90851cd09d14f2884bddbb586910f76f6a6f571101e93c060337b92f07cf4c66a0f0a6a4070bb7ce3ff3b80db29fd2721d9ebf34bdac434b72ffa300c95929134e74b9e7c9e0f7525a58f5647729311fbdb9ee3de7c0b1de736174c43c863dea182b538f8bf89a912c234a3eb0fbb043cf3d4df36d613122d4c99b949eee12eee7abc3462674a94d774844483a7801ba75e90eb9768ac419d890cb8e23894208fee72afed31b720895bf33e77f900312b9af81d20d0def0c45454bb02b33b5cf6a17834628889bd554cc56ebd6e1d6b5dd161a0f5ce22385f67d2d6738329bc9f1696ef9d4660ac21866496f5e623d7b8523ed5c251ac78105c6d38b27f594c596afc23d7dc552fb76c27703c34a6e5aa8a81c0d1b9a8618daf08c59aa1a67e0051528231065fab92dd6c840f7b23c211dc9a2639c8ecaa889c6fabaa13ba8a48d0b3e3fee31a21fe6b2f8da1f90fe40601c69d17cbd7482943b9cdbb48fb32e2a94821d2365f29dd9bc12effa7fce45b0a5880c9626ba8eb5b9090849d812ece7a08ea38635da9fcf8daddb5f87b757d089375fe0d4d4cb8e707cc382bfbcbe5a32ac209d9385241a709b9c0ee9074e9fefaa882950dfd05cf05c2e3728057097a075fc1a1b686fad39ab62297f2e596475227a9ae58d860444f4f86c3646404588054b37448d2f651f95d3c32dcd01259f73408149a3c546ee4a5e32ca407c546fbb6b3f44d83cefaebe050b34e2cc717cad3bed0f13c315080f11cf4658b0a902db27a83153721921ea007bcf75c6fd9d420f891ecf991a78f9f3e78a2f5b4d6cb0211d953acfe962b9d403e0a0f957361f967d87ab3d1ce7ff4d67b0c777702a35148eb1fc3566f7c7743abf7486f32c26a621db8b4204c5d94c397fa06463b0b2b6c0fa4d29319a8d5c82d4f97b5fc965428c46dda875d858e5d2922697a2cc6aabc055699393272f59586f68c41151364cff9f1b35994392393943cd2ff3a7346cbba4709d13513e46121ef2c230cb9c09d001069fe772454a74842f579573458750b87f9f46ac5e5e4a91a15d4d03b40de4b10c71b8854fd8a73e24b9f698abe583dd2d938b9d6a4e50ef0f0f25b703623d740be71fa9911a219a9614571a991ad07c1f1e4c79db78d050d9a88327d932a03255f3516d3ec9c0dd7cb79b2c4a971a60682be2251341d3fe1cf296cb9a698851ea5f5effdee4f578596ffc7f5040eee6c7306d6068bd04fbd6dbb2acee1c0093bae1e762b2ca98a6c47712b8ebf7c00de82b89ec1e7fa3d11e2e7da378ce9ce0338e3cf6c03c38d137d538fb499e59ce960ecaa93518ad82a52c57ecd0312805024fb6551be5371f3c3a73504862a42361c9765b9d14a50c8e51ad7a4274177218e60fd4596a4b8783b46217be92865c11a732bcb2a11bfc8d5f34604d473fb45e55eb48117aef15e9d7bb9043a5c3b9ac17f799d1be48e3a0d1bda97eb92f0ff8eefc8680a71b775eacd99412c002033a27778083374fe8d13df7526f13c512f8ef2ac87e35ffb7f060b1a3eb5fe0d93bcef571bd0db446ba9a28e76a3c8c1d34a05804162bff80e60a4176066258d1c87bbe596b25347d7c179c70c7ce626c67fd132f517086282736b9507f31d6b2e18cef4ba8e1f1a94c2d8fdf8b01da705f5615a53238cc370ee48c55c537e65dc4f4be1b9f11a8d13d6c9dc1745f376a5add87076eda2ae217d4fa7d552f514672d9b16c4fa8a7c8907a31927106327e4f6b25e587d3968a9031489dc1dce60819a71b14cd4fb7708d965a6f4e76dab696494c4b79306f02049ac97835182a9f4a38ceeb4e682ab20e1b35dbe4d74589581f76e8073620d74d8b1949dd7fd71dae6b933ca7b6fcceff7a811c1419739fc0b22be98a0afc4e576c9b338f30ec943f3a3a4ece9516008c98349db37649fcd9d5b5e4d5161bb530e507e5845b7aa3e362e556385a5d2c64411dd4667c6516792994bdab9e91d0b12f24dd0d53f5d1aae7cd0b0cf769665f69baa76364db1002aea2c415ed462d32d8ef127b430c60149fe50d500ac64e306dea7be75722c02b696208c55b4010491417bd79a7b62e0417d9af38201445baeef49e9e0722c4a9ce78074146981afd0c462ba45da0527e9f273bd3d87b1e75edb587193ea9132c990969d5c456129ce7a1e8f296f6b6c4d989ba93a1cc741e4a7d2677e07e1c9a82bdb87dbd4104456c217dc91a9b58c00b64c12b280c987efa1764eebf6454413036467701afa4a6a8f3e9eefa3454f56b98e715e35874042646258fb175856137ef708a7962b18dd42eb83f64c73178e640d4c70af1e14fce4026c82b50499454b7fc95138cb240d7a9ba62d75d6bfd7301232e298920bcde40ee33758fda065b1e80991f2176f93ac0f8d4c14f1f884e9d93ecd41fd19047f0ec9e2419e45ea38d0bd3a5c3d12bb3e7f8b2a31ebdadef073dfb6b814ba7802c16688c8a802ab236fd3e89d25f5a431052f906069a811e2af822c3d14f174bce6ba2376c7bf58b85857cc9410c20fdb29cc3b362afea1542ad1254e5917ee4b0fca39823f09182a8e673bfe0b2d181854c9ffd757c69f923a01ef82550051c224ad98a968bb27bbf0facf4456efb3d66b812dfadc4c2eeebe55375fbccd3f1f1aa71524d7ecdf2e772b04cd1441a2ceb970a772df3257a9a4b4d45d83bce1d85203f2189b6d00f2ebbf717c02d945f6d14e08d5151298f885466f2ca8585157f362c2297abcebfee2a47c05df7d378f97f9476d8e1dd7de912c64267a14fe78da2ecb7cddc01a681ddc520afdd6a04fb891966fe850ead0258a9106aff2b4980e9ee89b75d89a483a647683527dfd8fa05ffe3f5c6f62aedf81fed45ef8d0f549d50c6d38debe90314bd2f0035ff6878a853fe14defb0b419d1542532778e94e2ab0e644a196f25cde1941f8ebe61672dc634543fd082dc6162c972137f3794a516a28ba5d51cdef3db344eeb0d131f53aeaa52c751a50fc7fc17f8a41645b6fcd82d6f756c8409a614cdc6dc3608d705c13f5db253c41523cc853126187a9976680a7e9aa946581395706d4edfedc8f38b45dfd29b99ed7dcf486f973fed92c482c29df32fd7d750428ca82cda580ae784b9d35e9500daaa6f1b379babd58540f8b876302db7053460ca363cda4c5c665359a55cb6ddc264423bc00805d01046493c5a34154dee35172f002621dbb1ef07c48444e78bd0b0441e42e3b9a672ec60f486aff77a2b99df76ab05eb4f2922b72a8492a6ae8f7a9d3cc001e3de4718ed3353590004ce1de1b544664cf798f84a151a8fc4de24ec64544c9b47ee099d11659bd319ad5c2ef754e26a8c84615ca1c86d3302d89ac7eb7e0bc4f7b593dd45e11be8ad7928c32c637e33765882eadba30df98e1a534b7d5c218fc900f665ddc4bdb5ca32b8962bb7bae82bab52cd6f09d958a03a439747bc65d086294c21c77e5243d1b0edacd3d118f5267bd3e93d9f536331823ded26e3c93540a6004862f3777d2c1638db4688947e34b84ff9038a4a284006fe64a4362ec929345360dc54d3caa70286df0082fef5ae2c9591597faf1932d2826799ede4a3debef962bddcb42b1fa1da44010f960807b0f3159aff9e2e9ce9180eee65f6046bc7eacc42c1bea0f688f448dac128f1097fc23f0376926aaffa29d538e0556e8a486d0476bdbaa53cb80a967350ae6d3ac5608c85f293484da2ec290363d348bc1f298a168bc255c516975f653b92236aa0b31709d42bc7f7854b1b793d791dbbc83e544d4dcdf1d726db040cb82b2f2ce838827ee24fa14bb17036763c9b3d75e2321b6fd6a3c45f3f4e5d8c6494a141ae6472387d49e69e39403942b653fec5a8ec91d2fc36423d19357a3377be1bf4ad201714d2fd7d2175961fbe636678783921cf90e8b9324ca48792c0b6299eef98d12efcbc51d5334c02b6dec698c59585dd321c8fdfd7ae0a43aa115699f6c282dab051fe76f606e5169522607d69414abac7f2819a21d1c8c060603c331b929ebf659579ede3a4efb5a47d1157cef3c99d99d44163e6c1ef7099c440a4d330d43e7d39dbd9da788a7b567b62eda5dc8b3b402662cab727a4f8eb07487100067ffdb0fcab98b1d1a6965c67ac47317c86dec213148251ad986bd0435d0d759c3cd8a21837fff8227062b7b207a99cd5808b1079f758a2cc8905cde94686ef53df792e5b5961dafb4681200231e1135a902e669039145d594aee481e39cd7be7c9107a8137614219d4560b4fbe921aec68487f7a50800ed055b4aaf880e78a6c4d609663869799ea98608f2b43728004c4ba226d6bef3de59a8d3dca9df1963e762082cdb9aa8d97d0bc04f81cc3a364e36099099a9e22b3280dafda4c7fef0ee32b00989080e829173ef690048f5abaf08f8852830547de13fea5628e5d85ceb4376e48edf1b509402705dd87ba37240df3bcbc2fec2f89f3058c2cef18767f2b966acafbeff7777a6e44bbc24ebc6f5db07209cd01440a0ed8ee17b17ba2225944f999ec60ed0e85c3850bf1ade98ee7233123dd97233e7295d27e9a491f19c1222eb26bc6a7efcf18a0fea9c3d09078e0f3bb3cef173d77fe8a6b3e5cad170f5d09bc5edf485d870b6020aa5d34f339e5887044c640e25264a811a88992a2da88866434ffe685c160aa7901eb72619bbb2f084cfe349fda8b5412ccc9f50702bb3e4ff9fb3ce83f6612ec5323284c9156588f8e145c13ac7a089b13ccbebe439b46caec54643f31c619b43391120828bff7a32aa9d87cc12054ff1857ea22d20a15f3456d58431328cbba6564fe4576fc9abc7d18d0135e9ce513151e0d24534a4861661c7eaf2e9b7f656e2410708c5a47f140273533f5f1c2d793a2da2215d0b6a3e19fe232f03e83f37569640ea6c4a0dbcc05a3c84a038d3463e7e5030de135216653b7f3ea26ce68ac8762c1a8a4d4e10199f2faf14fe24e52cfac8cbeccc880300b360846340e6eb876ec7c560530f48c868b2c3982ebf6202ef0413e56071190213e7e591a0046b369dbdbd4a29e9f4210a5dc9d2e546d89457dc171c6a66deb8b3e8385e1dde0100fa3b9e5d5acb0536c239ee531792ad256e10a83163735a4439b7df53fed8b0903d5008946c47cbee3633a9cbc7d92a03e6b71300296f92a26952d2097b5feb057e5ae09e842156ae824ede5269e19ef8a43ecdf00b76d45e3ef11093064296629405497b675a89277399e74130c9a19797673ff7ca751ad68fa687e5e42bc861325240447fb9db8b4af21d724e46b561eaedd21d46297b028b86953a2d03108deca944ff0f0fc395dc80bbde57d842c4ae41e2918663f3ef2ffe97514db65ccde87beece30d263f6c37b7c9bc69a0709b65c90e82c2435a30ca5d6e3bfb78c682684aa0dcc88cdc9861924ee9c71c0e333312a5a31f010188ced9e924626a79cf7b0aad5825dc344456e7c637776e7ca86fb104ed6b29133fcec5e7e551f13588303f6a12bc166fb334255ee0c284e8ee55bd8bffb9092889a7ae040496cfcb49e34f903cdc546f6d7fee46764814a7619cbc5f90e0b8ea3b40518a1ef6e4438ffa9e0f89ca48099d2bbef908f6e32d762730ba77da4a807010e5cfbde288cd07b9e42fdb1f56aaf0bbd9f9bb7a1ce3b5af23f64a0909e642b0350a6cf9995da91d5acf10309f53706b121a9e1036e213be94ecd23e6c6122a781f9c20d717a53b896061007a46ae50b8b52b1800c435fc3085f55ae40fc6b05f901aea388311688892d64b536e2ab3d619c340d243f3231edcadc5ae42d9698da5c18cbf95771280eb7619a03b5b2021eca3efece3203ab290e76cea0a6b8a5782b1df94f09ee8a0daf7b0e62efd05a76ddae0c9918b67c6c6e0dd403d91347028b7b4d616ab91369797c3fca5d18bd727aafebe12d9bf4372d04a8c4d77aafa1bd2fdf5fb7e084bf23c874912bb62f3ca0e3e31c2e4dd2fbb1dee5d3f6ef84fcca5b9d54613e007aa077e1e14e2fca173da6c95218adafc03f6593754e4d363b96e2c9b6b36c2f16f5eb59a72f4143806ce6420b6d96beb3de59cb6dcac11daea64af4639bcd133c36660e1abefa3eebd7c6db37b1db75ced7a459b60dd383ee6d6a6b68e2c6d11e1cf6ce50e62e2bc1fe3e8910b9ef172673b8e210fae85330ea803548bc19bd17df508047ca91f753aa96147174f2aacb4f17aac13203b67d8e99afc175c8444624ce4ffbad6a009dbf8fb1cd71a130755f1ed1d1eadd2f71869686695597444389996f6d9ec7c02a9b2ad47c6a6e2aae962d21d7f41750d1c90487c1cb654d68b5a3f4ea89add4aaa54d226e4acd2c28ae989b45027d02d5279f5df686a8306cf4c4ee10aca48b8abb1ecb47a2d0bc8b63948b4e979b6fe9dd90ce38c21365600e886f75927193ee0b110033e23e939f722c4db27b1f3c67dd2a6bd6efd566dfa60cad9c9dc5c4d7f833603469b5f11d36b34e0c787c91bfe3088e3e97373de81892599fcb45f6b7cfaaf7038f271f6072cb5ee6943502844957acf67470b82d2b643567c84f432ad65bf1ef6d2ca6f5862c58b86115da74b8f8a7e60d68f8687a5cca5ca2090d52b01e5069c7b5ba4c86a6a4b1045c172ee2df41c0dda3a5141147990637785cffaa2f1e7a0c6c0c9442b3635d86aab4592701752a29f7359126725bee6aaa9a9b83c47f77522aab24c03a9e23b73883bd981ef9a8b1b95bb989a9a644d9faea5f9f1529b07b5346a4d50822e7cab65094a0912b7e3a10dbd1b6fdc38c09395a9b9dcb1a329a8c4be58fc015a7d6bbfca9c4aa8320f2cc7c7629193c1e454c1ebc5605e36d3c8a30109721796d40b9a645baeca189ca64d411145ebc80c2c9d0a66a6762d673f0294076407117a812ea38970c809cff8352558f29d93288e4c56d16cf47e06e0215293d591c4c4705c6737072708899688fa2ee969f52500009ce0e3693ac3c4e000e52a5d9c2a575630b442bf1e9418878ed14f394f20960a1993e4700766491b1ee19ca906e121bede4b50087b253398de8788f3fbe814fd648027b35c166af6cf31593d1c5af2176e2b02fba63460c8abe9fef3a834dcbd1b17f81298b5bd5f1785ac3a6b4cff096a22d18e5372a7e50aab31e68d44e92aed114bc114b41fb94d3f388ebf13997b616638552f6a599eb58c978825d5c37760be0e30f6f634569fb2bba559bb306aecadc99c3f9ec6286df37f00dcf3baa410ae37bbf26e7f6ccb365afceff7403a00d6f8be21acd819e107ca16c401dd3860fc57bd5ee05f772e22e44612507e3f08b229820221b27ddb9e169510e09e2addbd34c45b66052a1372a7a0bd1d8fdb7d1e120fb76ed954104d350d3c82b4b394221ba35e045b4ad7071e3bba7e7c6d54a8d76a82c065ed97098241db2c5f4877920da13e08d0422bc6c7b0fe5f246c1533042f0841e9b2a4de6dd14c10c4c8964342f6c58409beff657c2a578e66cc76a4475c2107d7bb0f27f5e8f970956cc7318ac29628e1af139c59cd0437e7bf9497a2dd8d51288a1c7f3919a1a10b909402869d210d420df579d67fedb214282ed183e74a7c4e71eaf8aee300614c4b6464942b8b7f24b4f6df63231aa42d3af59f0b5796c3cf450f6ea95bea7e22379b065d09e3cf48039e956cbd29958541ca2e75e7400efb8e4f9cf0f81aefa42c9f4cad2bc56d14aaa847f18dd7a3f50c20e3bd1f6188bb2806823eace82c5bc7159ec84b771af87f95d0e7705365931d840a75e1334678e8473780d5387ba771371d8eb322ba7f82f55ebe4eb32e58f863fd5eb6158bcf483a7bfecf0d6ff24a14f9d08ed3062df3f85b37bb00cdd5c7804de6e9995ccfcd9876a20673ec89ea11d62ea92a8f5890e075a6453ca266f91fd7a7287f6ba74a3f1e3bb8864d5b6a76808c907ce688818b214a97fa161befe1a992267482f9f83bf227b9d50295b026d8463cb1d683a6e910424125ac0d6e6fa82f78b376208464b67aefcb0855cdcaecb1b9fd3ee9eaa5333bab415495b6d2d8b8e51f524e728d3941c747fd9e0e56d8c04876c0035a0ea24908fd66e74b09ca9c3ddb8546608b6009af53884934b205e5668b60445b59b7baaa1dc1b8450b6cada48011ec7cae9ed7f70d7c580b5ec390e28e13a240feacce8298fbc297c88cc6fc0b0040d217fc41ecf93124df6c66b391ca94f43f494941c6bba8fa27bfc85876fc5fddf93bf57618856d60f723104f2b5805b9a16513558e32ad4f9113d85cc16c584e597d6c3a8c01f38f5d9a093b9d33cc46fc891564e06f8a018c9a87f6e74d79d1f7760d35b35865b774532b7d7949a037e6673b6bda31b975bf7ba9a1a97c5267a13201e008145589e8bd2071fe1919d4476c52d059a42f0be2d7f40d234685dbe45e4291e088096cd52970adf926c53b6af571cd44ad792b471602055799b3e1c24607ab33263c7d1ad2ea2abc9cb4027f1a17843f8ca9b9c4e3156739a7be870bbcf6de9bea873e4c11abb132821f2136f429d8c699d4a08fd4942aebb31d83f4209857147acfe82aba37498a593521b1d0add35c95698933f698e46899d97c5d33e6b1d35e404b5a80ef04e48eed44f4095e781bf2672988f95ff717e198f35a67a13e6c18bca502dbe34a9ee62e4d3a0f0b8517639cb87b4bc8db37238ffad43dd77991279e52890f4e10c80758a55d4795ddebacb3e88282fc7e4d89c306fb373845ad7a4e7ecc839aeadbea97ec74da3cb122a5e1fa13937efc7887c5f906cda0bd205e2143262a5a298ac31eb4818a600dee1f12f6d4ea44dfdd44d1f74884bcd6bda9002932e5a0cb85b4fe329a089845843c5cb8152111866aa202a1f6809e8477bdd21c43201f2150f2435ef0a89a58d7510140a914e1c7d2856b7b2cfffe9949748b0f462b130ccda620999a0681dc17fcb9aa6a584f3f0a6e166d63639dbad22f3bf7bb3c866b78b6e772df84f4a93cce246736f0bc682f905c673cc77106ae58bde43888ff51880eda4336f1a5ced3c8cc1a7f269d3208d6e9561b76c7346b6ad3dff7f4c8aea82fe1a447311d7d9c95632943cca8e56d2e9b0beb5770d2a6258deb1164d0e40987222432965d8109eb0c5cecb43377e255147c96ff531c2a5a82c2039e1b5324048bd4e2c7a7ec34b8aca711687e49c26d5d0d27cc00ea8b607189e84d7cfa806d5e511bc4dfb2f2d5f04aba3aa0f862338a2602196860479e158150b44b769c53c9928563bf0bc15aa83701a0c753847c2abf5c5d4fa9ccadac4f6ec293957669f6dd4965f133e2bf4554e2e12f09ae36438e4bb916f308a548764add082cf31f0687e2ce140d8b14e7d3a5ec36ec84d0e8f5864e8a4dad95fc8b703d8e466df280a8f0b61eee1d7a93919442fdb3065e1f3febc261e7c5519f8400cd30c9c6b80a1a22b72468b6689c48699ac3644f1fa0a4fa71411d483bcdf6c79d06ee3de499bc6be8159ffa8621c2b43d4e21facb6c9572bab1a2cef6ffd83140a9201188f6030612557fd11c14481abc4804b0b25d795fffa01b253a7e1a6f8613fe2c64407052ae272ce4ba46372f4e55a682aef655d9303e2b4ad70cca5585827ad5dde12817df55bab0111f03833217a3a607314c73857a78a79941e9ab3c6450c759d82db278452cb760e11cc03977513e9c5282849e8d9c98a9e48d7c320be1a9762f4166afc907a1da2b4a33eae9d33586ae1ea23323e079294c0cde9e66d5462e58069dea2e2f53f6c2e891cc9dab5e0b8e345a22536e229e7b8085f79ce169c0316663750a3c17d8bf16458c984b98fc0aa814482a97d37f010bbddf53a51d0bf7d70e49456e7896152a4db8e911e5b0eae15653a73cb82c305044c837d1970c9e97caefd4f17241b9ca7eb1d5b8b6b20b5bba23cce4b4559d9144fbdd03997aee9b6df4985a8e073c43543630935390d57867e0ef1f1d683a6176735d1ad8b48078678a2f088d9036bf1cbd64d2dc5ac9821e86415b395c1299eee9b5fb4b380f9698ad614206648ae6441b1e30ffb8f5271e35b610c3a34e1bc0bb00cf9eb128ac7e10b54c11b1eb35d6a00a193e52bf31e4231969ce6512d2b7676b70de94f743440d97b5905da0fd69ac011ea83114e4d6e6405679f7c53468cc648b116f57e5ffac116286535634d8db4c667220cdb64fcbdcac1e27552449330114ada62d9e4072b378f7ca5142239c739ac3e059c86ebf308647a153a6ce8d133fbc56ba345a941f51a8aea6bbedceef2061986d0b2a0ab0c9a8220bd6bfcf567d2deee4b8be97d995fd3a9aa7da0f592a1c5c508aeda94015c3619009ea072fb37b5572a725c05f5b6c70e913421992de0eb44067efe58e3502453d6025bf239fd138c8539155e9b88f37772b27f4915c3e391b35ef6054c54eccd375d9078c1e2850382f384ab16a462cfd0895105b6824ce7ae4e194bfad1769f75812da3c41d3c1f6d759497d90a6e1fc55ebcf57059e415b4c576dde798ae4d93210cdaf2094812f2869b0f1259e71daf4be3cf96825d72865d40940f4cba7045b1ff64f8b614fb6e86ac6f56088cdf1a00b47f3b129e6175690d219ed091b2d83c385c68ad4d1f3177164dd8fd25e7ef9255a880ecaff44f137d6e4719b14603ccb1a4ced61827640255ac63a895a598783c0fa3f0de387433c08c21ef9b8820b74053fe72989bd82e08875624518a762c60023512a85ac8c63001bf6dc9c84e1997a62226e9f40a4998a5089806662d91d9270e39268b770f4e9be5f60f00650acf95e505e34d236f2ff051c4f0a4b8653ae89adb9029d205156a36c4ea80e81b77b8f0fced4cd0860207f6ccc5e2a86a2ab2c3e6c5d9910d0f149d33d1898d8473ccf6f9e1f2e25491468fe4cef08aea055db3e413e7e200e00b45c9e39b7f2d7ea73d397266b61f5d681449466647fd559f912f1484a591afc1af8459c0979e6b69675888e1c5d72cf2ff478807b25e7c42e86d20013c3e7e0f3d53152c600d64ec9ad4b68a396442f2c12e3bb4c63ceb5c0d5b98b79a640159bcd0bb69923234f5ff414bdb03cd80a93f4c415e846f5196bf0285e7745e35bb8a11861f463ec5e14f2ab8ca2f7f69a92f2bb5b9ee1f86f71ee4f481beff28a0f504f0ad5cace56d0e47614dee1497655aba63fe6d72aa054ba62801af4f84e76d2349d0bb0e5ebd2d652d8102fa1a6cadb0ee94d987b9bcc667bae85ea636ac63c66ccdc7b8fe079c8f91834f20b80d829355e6ca73318702d49eb5e5a6c77c50de54819fc804be0016ee47d7fe43391285763983130584cdf9d452d18b97a3921343b482f5d6e0bee27249299a2a9a90ee1b1988b97f5766b568373a55681dba83c7e67e82e4e34517ee06dd64ce7495cd131714cf0297d1d3948545700cd53d3e6dcc76aca1a2cf339a026fbd54f4ad588978ba13c9c51d9ede6f136f4a75a7877b90a94008b42996881db48d019eaffc04d802ed9486baff3c6917c1ca95c1250d172e48e8265eb7bff0c9935e52712ad142dcc782cb440d5e094b285f9a1a09595d24b56ad8ee3062ba9aa7f1f9586ac479c6b25f8fe493409e2d65d6c4ddf31d3092edad71a9adf18b76e95a23fc2e432a8257c4bb4810e32e50a1fb9ca2984b9a1a00946a81831d86aa37c48955d1e8f645b050ccfff31ee4feaf58be515cd5d71c8a3e7aeead7cb12c8cc03fd6691782fd9bedc291021de08f7af18aa44f15004826af2ff9b5c5123a7fc671d0bbbf0bd257d3d055f086521c8c20bec46dfac6533eb88eeac51d3654fe7c7b03dfd3d6028f8a56fc8916d3d18d674175fb2fb053def35a4bca1d9555223702115cc45f0f2aa970691ebb005b67513257ec80e90a263bd3c3120ea1ff259f139314b236de1d55958664248ee4e991b20353c053e1092864c392f2344bcb6bd1ab0dc6524ec77eb951bcbf1ffa55bd5f1d8358da3a47e2e6b6a260ef9af86123b5ad21c1578736088f6deff8fd10b2a4465ce4fd4b0b36e32e64150a31f277b1484e6a0553ee92f9c2c2e435ef44a781f648f0367b0b6bf5fd95565e33eb029c2d12e80261c5c763b81b284475f01176d2bfa9a45d5faca635c5ec8b19c728289a16f1986730cf250735b185da6d0148139734545b6fa68fc0df7218cb0ce7a8b8a9d1e040a34b45c4eeccec9a2b5940aec66349e015ae43e99d03ec1f9983e4ca6cba00e02563ef9de25e16325cbfa0cf538ba0e2fcba8725e22c2ec5ac3c79bfa83b86e5b80e34263766fdf485685d532f8a8440c63f3d3ed7ab3491e0ddf7c50acc0bdf470794ed7c55d95a96e3f79e87ef41c0a5f76c264fddefa4a8544542830396e27d49228cb73ced50867acd6dc9cd80af962362a5e8efc38409f70afc748fe0c677baa35dbd85e26627dac07d7d4e65f37e5d46c906231303ae8d45f7a5d4e308b55beb8bd9b8fbbbf8194e3cad8e1168fb1e94e93612f4c24d7b28d17af734c5e91357dae3c0118678263188ebff2b90f8f723e88f5bb592da0b44fb12c773842804ec740987e5d494ae9a27553e543c79d73c5d4d8d165ee7bdca5604c3f59ad0bbfde6c613861bafa7f2acb1004fc81a7c1a54ba5b1c93e05a8ad375848ad646dd732caee22f142eb3b4cd9c18e6417e4e5e6657d7fa390394bae64ee66184886e3884f24f4dbc1b527f5beca201294f570f6fd25780fda96d5de648afbee9ba8ac9aa478ede53680fbca9e0846913fcc854e063b3a517c0c1e4e76d1e759f0a3e35032f8e181368ddcd524fad0d4efcd638710b69144b211b3cfd3a0bc38b1e66cc64697cc0ab52938baa3577814f0eedeadeeb00ed80820a3c0f0ecc6fadf38666319879ebc9e577cdba117e7dc8f6153ea40072b27e0e859bed39323afdd81d02caa7fb0aa9eae4ebe63e10f9138cb94294719d0ad41dcad1817977c7941af7e602560e4a68d908517e2b4f85f47c098cf3dee9142fd0a0883148899c578c47b31468dd8d3bffcfdf0bd5fce41768407a39697930dd505d15d78391780f516ec1eea2278fa00014aa50deb5144e6eb65ac12be996372868b4f04821396c876c9e33478dc6b38da41ebf1639a402ab23ba3bb18791dffa62c7b93d41d34796f3f18129f6df41b10a5fb07c502913b95c9922e83dec2e09527a0a849a093e2c3c014aec4b0b49fe05e2005f39f3836b8c3bffc26a7b0efdad0591df04918df38aa0f238af83c2da7d59759343a2e4d51774bb8dce3619b58354b265c18acaaba8c4627287b4e36225fa3bfd021693119ae6415e3ff63a62263bae1102807111254bb2740c786aea4f4d60594ba3cb6e34c2d6bdeee7db9ebd4a7fd7a141e4addaba477a6041ed56cfd42efc7f0634b9c16be58bb5151cb8fe336271d24ae1063e281454727263ba4d610600a1b767e592321db783c8d9df959ba405540026a8eff30836b65c1ae950011f2202928abe8b6ec2019038d19f8c14978c849ec2ecf0ddefa38167056c0597a89c11ebe84e4cb326ebb5899a7fbff8c584899dfeba751f4ef5ae0323135be0ef82897b0111e31c3c5d57dbc7ed0dba8ba2a6be7e7baf26a241b142c30b9e92a2dc3e6b37df986a2f5f8e4b162f8e255d7f2bd60f3058a38057225e2236a88a3a0c57ada80c10e7c3b27457a9af922f2761a429f582dc8eca578ff491786c474c7509ae274be682ec2eb0fff67f2a5379dc63285d7d5f7aeb0d1b552fc7b1f283ccc20deb589b1f88f57e5849de85faf8764e33f020b18899d5cae469ebc56578391f6ebf5490b18dda97f096defb30649fd781d0a266352da97d73edfa3f946e7d4c0c54223c1aff920941b5374fa159303979bcac63f552be7fc5bf5c9d7b6289940447376884d6aba8e7da7d1bbfa10c1e1efe52f7778cabf054b6929a4754213d601d4bc69a30dcc3189afc60fb4163f1f91aed20f121aac2e6b29888468e794b3e5cb48b63dfdda31e264eff136d57a203fad9ea354b4171f032f49cddc13feacf6b079570a877654a64923db0475ba610490405468b5b472bb852c8eb3fb8d9e6b58f92ae1430e2f1e4e3818eb025814eb986d7e110e819217781e5c0944759253372b46d1efa5133934b3ccb0bb5942a63bfe861c92ed370977015a94790a15960f552216fc2e9a8a5997ee7fea968280c53d7862ec026e10647335219f5fd1688b2f5a8c180005c9059a464bd6aaaae8f58c5b07b2708d9646a954a68090dcb8cdd27ae9eaa5d81941b3a8e8e19a734e4b5082fbc136c0e89df4356bf49c011d11c28793a6a8f414d40da2fb135fa7925ab70be2882ee7c6b7927cc36287fc00ed94425dfc64e2fb00e5387eb473856153dea2b99051bc3717804d0f05a587f867a7aa73728a5524652a6311daa1f38d709d515f9e73eb59e598c397e832b32386b03cb73a1525a9f414effe1ad4d6ab6a0833c5559746d4936dd5a3da2e89e2bb98f0573274e19d4fb09f87acf5f7bdd7c5901a550e91fe21203f72e97dfd27bc939c0e6b6aa97a4807e9c6bfe3bc3efe95f46348f0a4d3c13b48cc7cce4e63be4f631a79c05d187c4e293bb0c62862d652bb35bcf273bd6783e4f590cbc25c53f1307eeb732b1aac51f84b0eeebbcec20f6e0a532132c6e87998b914ad61f8d2c65ed0260037fc3d8986793ceca1e88321687ad0fb75e6342d4b053da2161c64e98dd4162784302e0d377d07833ed30712b1f6c170b5cfd01c033a55ec8b69beec244052c4291b3409db90b309b50558d47d64389a7d9a116d40666721d6d41fa2f405ca8bad660f3e195bfa752fe9fbf4697eda532317e78d8cde8bd9273660f59221470213df6d7cdd21c5cb6763480c51558cf96a6510c6d5422d6f20e039ad4dca7994a8a127f77efa0de7d4c7be9a9add1db7123d04539264fa12a88393287f03dab0e04bd189ab92876548df3ad5fa85afb36d78a2270815fd94099217956dc847334d12014c06d620e9fe477d70b0c636eba173b2e94967458551955adf06e10b62a2e36c5e18c61c58e2f202f8648d3cdfeddd154545426359877827ee433a309e6358a4df34620d492b45f91f274396718655eb916856105793b9d8a34333358abe291682507919921b5e95318eb02f3e8b562d04699f20584c439c3190ed984641b0442af3839a6ce5492acbebce81b7d5a868662129e74f8e93fd7111bb25fd44b81f54031253d51339faf50c6706c98545f94af7b362942389a793dbab4bce5369dc410f709c682fb7bad3ce268a32f8d56625a58b4d811c1d73aed18fdd5b72cd1f201477679bc15bda78f1110fd4b430b4307ea278a65ec2a417c93a92438c41d42037fca9ea10c06ee945d6fdf9a912be7f44d1f625a1ac268736075eefa89196b7b5837ad5ad5da5e064f5be7b95fc6655f276d146bb0ff015cfc0a7c72319b3a4e63d1387e3b29b28dc9bee7eca259cc50a8965aba27108e17417bc9686aebdccd3ebe9fe95b614e4e0eab649f81be6f09841a2436acb5cb7dae13a89aeca837827c01555f315f73131ac04529c6ba31c0f19f2243e4c0325c9c9c77c3206d6bcc5a6f84560c02f4aed20952abaef7e54f18acdc8040da6eca6ec1afa13021d6a86a4454bc574d97f3602db8d0da28974c107d0aca2ab7da30dfb3c32b8c1dea0199cc4c9bb681570fba3693ae2ab9a5dec1670d37cbf2dc793d87be0170cd6c296e8330c8da494e35f20cb923ff7e2e3d1863ace242c251d0c44fff97eae9e9f0e7254876d7ba7c22a4930db12db55840ad0e0ad20ed68afa65150563046493de0a11d2b49cf6362dbadd993afd48a812100b1ba721eface9cd3f59f7e0cbb2c8fe29a5fca84a3ef8096aa54528a5da704b874257b973dc6b79de5485498f43b3d8e1f32fb9c427d82812cdd4486abb83d30605f20c2e9dcc5286df8f3f8776e50d8877801ea3c3a76c7930886e2708cc914ad448bfbc3aa49b0ff36595f74ddf7d4511a8f9a3667f408781dd3a1cacf5d32f72bb7109203aaaf517cbd83f762bf538a55997db205bb85f96429484b041911527806d59fc23e9ac5276396e5efa94f0cbfebfb50e3c13e18ffb0dd61906aa71451c692aa1107945e45b045d46901eec2582f4457cd78782afd3d3b16196717d8c85fbf0d5e5770b02ee654a660c909e70189be5dcc6a3c67de4a7ba6b6b5d4bec0266e2fd09c9f5638ee4f2cbe6661bfaffe25c0ddd07a1d9e914ae82ca51daca5bc73b58bb1dece473d7d0c11fb4f73d324d7c98df31ae8c93e941920c8d404a736d5534252c37fcaa9e5ef1787ed531b825b5509e1ddd7cdfdebd204eca849e3740aa06008e43767a6e7ad7f16b180b219131c2d91b1c96092a20ea82e0e8b2f1e8ae13b5e5e515d884d5cead0713ce5cad3ae03e493b714f5037eba6a45b5be5dc548ea7e122c0464463a79370571453ade52a7b7de02db4522d0ab64cda516a6857f2a06f6a5f6a1679585d5f6b1679355e6de8dca87c13801ebe297e619ecabce4030ae43a6bf77b51983898fcffeb4ee40f79d6446cd88361d1387c81e2158c3ef91b58946fbd9e0bd611e2e621510c27359cb9ed2b3ed69201b42c1a175fa58048c5df6ca38718985d454a4a22b8ea865e6439001ad24473b550762afabea4b2b5a4dd3fa242f248f939bd521857f087f7df3030836c8fcbf349a8013a91f1c64eea961541bd0b98f8313d8d4fc4dc30ae4d185e83453892d0b493e81175ffce53d64966a4b6c6996c36bdd5d8b2a280cb3686fec2765321d418e8f9f0102269f9de6ec54c83595207193c28e1f4763d7642f6895711d3c3266ded79916582109e50b4bfb65599be4e80f0f5c3e3c01c1b2246ad5583caa07573b9c10757962673e0ccb849c845a510cf3139e166f96d31e661850b69c0d8decb6c3f9846edce48a830fc9e59468af15890f6f3a65c25593e0dad5d3fb0eefaad8872e5aca3834090f4bcab420ebf5ecd96ddb9edc4c339071f4bbea306dfef1fa08d12d36cc4aa0ab201cc08022f04b464e123bfd90067f23146a252b5ae1517f57df09fab1b825f6d037094b724d320f19a61f2e41449ef567b841156d7aa8dba7c4218122486f8fab1dbaf4378e9777563b5342f0749181fbcfa55b166ccf430c5047dda0d07c475c6f37a0cf71520a75c91ff14d0581e15aa97fcf129a95ec1926747a65a3a04fdc6277e7f5c0f07d678051365b2e8594bf34e89fb74d0c24f7488bce99b07ead46fd3a8942631df46ecb2cd82184f75200f92ad769fd9b648343d8f766871a64c8e8a0218e1e14c2094a3f77e7204120c6870091f7df0bd019d158965664c3997a6b6168dc90816ba208e6991d87bff76c792bfa5c8b6aa3cd62808d8f94db0abf679a6438b6e50a92e7dda314a2714691eeedbea41d0f453dccc0c8d351174b7ca4dd87567fdd40ba0aaf48ad79926d00c3c44f06805c47f450dfb02941410dd063f61b1f6bf34b6420c4e770032fa28c1ae376504cecc94ece759a6d3f889f1ab6cf408c73a68d0251f47deeee216f903c7440ce9b5ca668bb2c0bd18b99d7b6492f158d33e33e7df083f5608747282fd02334e9e32db92a97c16fcdb2bb48b2f2150d1551798cbba5484840932ad11cd20ca4919562c9c76bf5fc7762dd884457d77e9d6a215359c3017b18b04d07f9b499e34ea116c5846608fcb409555726112ec7d3f6e9bdc4825248fd432d4d57bf653c27e37f41a4628bcd61d92fde58c107c7dd8f931a7b3089be337a4372d4c8c6cf4192f5d0094512f661f424a9c7e1bc869cf7ccc5548a6e1f2045ed003f28267cc9446d94c38bc41b9bacdd2152a2a77f745cde8856ca6e751f99bad89f82848a9f70909f883735045c055d983ca7d8652ae8f9b22eb0245f9d26e2942da825bb4c19ba1c713be521fda85099846c5d52ca55b81b4af106acb08ea085927362a8d97ad72ef080cb3a22caba1eb39c1581c1dc2ec55102f2688fc71293701a22e8753d41bf93556f31ee25cb474df8d3bed67ed6cb708479138fcc12f0d73a8694a50b4b72e3ba46e211c96d7b0c5766242ebe1dcf72bbf8b2573d1895b16c25dce77c007ee34ff15f7ff60871c10b84116c0609b90ca5b0cb2208f89b6d660ce64ff41ffb79449255fe834c0166754df0f1f1296ead71064a1fcb85b6d920b723498fbd13fce55e9afe6ab34acd22ec94b07975137738e17d0abb7080308f3f46b3c2b988f2f68161c87a3fb860d9c4055a3a5e16723ec00966da4bb83f966ad7e7e95fad1746e61edca8611459ed365c347e42fcde527170abe0f52ab2ca95b5c397faaf14fc135b31f903414e2cbd7a719b97482de5e55de36038f81315b45522c2aafdc54025fa697d622521ea7e4e50fd1aa084ffc476a13d47530e5fbfbc7d1a50dbab04e9fbe2669a09625376dd17570cc4e37c07bbb1c95dedfa2c0387b989d46c52380f4bdbf1c9102c1e460fb857d8eb762a101cd322a755a859f850e747bad4fead0ffaf56f438c0b1c5b43aa46dc4cb5fab977af7f6c6ad51180b569d601b2e1150016df3373e5fdd97ffea077af02160866fbd17f9533aa7d81ad7b5410acde6bb6736819c4e3d85412c3f1a1645c6b4267f91eee051332d942aa795705f021c00a994598ef6dec31a7e101ad9678f9fdadb888bef81802effaafb65e6f4aa326107eb637af3b67788959a1b004c9181ee710b6072642a0a3298461a80948400d4c1c4b594d453d8f77954e1031a10e0c50158d5b60fb83022d33b4d984d751a74f9666d3f3219d24f38a87f744f4645ffd5e1fe5aef0b33656c955142e865bfc74b53e3ce03a0673f24e7b24f8cd766953bb999e1b5de7a3e1a0ac817c89e9ba5ed3350b52ea5dd05106a719f86f0c5334f00443d031c599875ead32431355278c601a090819c6aa7bd39dc435dbf82ccbebeef2cb38248d26dce59994ce0deb4325f19921c9bc47da1b1e9317c976d8aa03560819a8d5915c2ffce06e900a62821bf61401319f1267106cfd36f95905b361b6bff82d02f2522d31dce465c69082771764fff6071ba16d963569d4d39de827444c7df42c36dbcea6b93a928c4f2e6f0b6b1382a2ec176ca25d9ffe341037c0fa50435676e8f2b9fbaacf118623f5e589832995cc0d7c3afd79a586f8db602ca71c78bd73dedec043d54366d39d6918b6f20ec393d322c9f74a3f97d307b11afbff58741bef92b81279faabbf5db0322081f0ba9bef6fb61d6444e759a68d79ac0753d369947fd65b1e715c94bf6a347323d70706a686977453d5b077272c65ee954287cfa0238d86d5bc4664c48e400547d4ce290e668176f9611fbdc292df55d3fa8c19b440eee156d17c837a4c27f511df8df9d5bf1a36bc627fc9933dc67b8b33370c0a645bd890ee84b2bacc3e7e48f4d61346a03ed32dedee5c10956ce728590da71ccba5210b54dfd7d8a31c47cb9e31780297b246b7a0919c66562a74f432e06bcefee03b89f4d8da01c7a3e8d6356d86105bf08352dcbfc29b6bfd44a29a7d0182eb2a417ec70a1cedbe9486445cd7d43b178d7a1b0f7fa7b810eb28239ffa66126d93b0afade2f83e8a4cbd938f7721f4dac5f6f0b652d5d1adf3003a654c1a324c0ca6a36d579ba005e052398f73e8fa2372cf27ca5b3ec4d0900b2ba162354e12c75cd493239e7a120118a639e2191740cd5a4ccee7f66dade15046f6a6d377105383d4c2b4a4709935b07c4fa2255d43396d674b44d80b8daf659b45177c8560a1d2a5fc7a0daa266b83db7176c1d009d00e07a6ac8fad1b0c98b36e9abf04164917937c7081bc44e39a92c5ec5e259a766d214b96ab57e53ee665cda517d906fc14aecf5f03ecde8e0ec741b19cf5d42171abc0b611f27dfba5d1292e0bcda972134925d6277ccc286b8adfac52b9caf52b417d78783730399cf141acf67d1d0d868b2fb70c1884dba62c34268a9481c2ab0fcfe852db346209ae26266e21e97da432fdbe71b7af693d497b1f532d3408447c27622480847d0cbadc44f3f3ad69b8b62d7a33d296487709282b228bd2fb975bab8d4f1fd48930da288393a79226c86650284aad6b7213ac2dd78bbf9ef97b7b549a439f6a4888cf34c8ce5e7d7aa6285fc7fcaa13e18a7d0ad27782ef26826925df8e8b51f602a6941c75c67c47767470bc60798b2081279c5efa601ef9941879b2df2576358b1d950e561ced885e6c201a90f0a79c70f2f4ce0c972a34b61c24c5a41b1b39d55b9c9f14081c68f1541b0402059e3d2cef51e86f6ca3d0c1df43eecf4e3bf535a5328239de7b3f80e865ce817d434a679949a8e53856f755dcde213562ca68a95e7835a671403c651f840ee2997aeff92ea486108926b3cfa1935073c55a2a8ac664d3ff106abe600860eb106bc2281bbdb744a226bdc8290777a8270c83e8ca3a5e1f80eefac448fbc7ec1cc04c06afdda2ce205cb2235b47a9089f68c9baea5524b428ca61c0ce97945490841a93a206ff5304774b56cb53175907e155992535af3d8bb47b14cd7b699db8e03e722b56a9362519e290e1012ebf9e5087d862339ffb0de7ef71db23eee1dc9bcbf306246fa2769bfa2a6c586f40692d23c3eefb8fa23850354af99b992bb5e9277f171fc0554cd96b34cb8650de5a2b1cafc6a2cf94fd176adf12fd16eacfd40d8335ee3357970a7aa0ed04a2697ada5a45b7ec72c77d83d5c1f89ca1f1b1917d4f66bab57124ccfa1e8d77cf28a44e5ca6ce8c0fb52ad4580484d6e7f50518d00636ec510e013abd6b36a02760aa1fffb2f9523a32f28b4b29b23e1cffb16b6e88c0d848ec06f2912a1f752044c1132720d539a0d03f1bfde51b67b80e90ad03a3d01b46cb40dd1c2e874c461f850175511c9f51f2fba5ebb36915ca245d1714ce6fdbf208df34b21f016b3ab6500101a2bbb4f10565537f33b357c0135b1da1e43dcddda2ca7663afca50675da4c24151788415249d93c62df45d85714350ae7b8b558e808cd9c5c88c96628802ef6cc172d8ea212cd54a0108f80439708c53afecfef651b6d258c8599c1fdea389fa85c046ed24c0474927ce1e6d92e5a3c1096d9ae4fe27c0ddea8e727b2e97a660f8101d0ec9416d822c794979d79b70be5785ec1d11905a1c2858387f5b7a7b7684dc3ef1d1b56c6bb7a81dadcea36752abca7aeb2115c20e7c1cfa8bd4b7b68fa0f36db01a635d6c59cd78e5b0b84f7bd70f3f542f20d88164e6d847e29c964ace2126d1cc88a18bf432553f1796c85c323796b16f2105e22cc7343d3ff03214be2573cf11e1667ac9d54e6513b6f282e880c87c4076bbe7baed4ad90b3bee93a9b7fd58f9ec1273672c8e5e5876f4e52b1a3004956a5b2dc182a1ea9dc11d4be6c1bfaf96cbb32e06f0119ca297dde628149f4f2cd85f59585f3a461f5031a072e995711999a7bf69961cacc1edc8b3ea6149b264e109642c1442fd98e1a1c08c94b689fb35d2ff5b68ff9bc0d2a762d8c40f6b049908da208625b7aa436cd17f23c6050743c823bc06cbe512d95f8dbab15aadac9aa6bfc53589efc18e24fdaac110edc99507c40b08582cc9963911c7f90464ced31460b84719f0226955c205a6b2a157bc08b2acb46f54fb69b21719b5ee02087180306eeb6844d91c1f8ac0c1a07a3d08b6578a334c9a8245379dc92ff011d255b2a0ba895b4723c692816c5ef02a622d6316a9c6f8858169ac70511be88e2f729d75791d3f3ce8cf4f0ab956565b7b94a1f602d1770f929527c81b70f9360a22c5694e394b21c5f4a7ad8da395fecedbf0eefd21665c7ce5f239d05aeaa66a2b520d1db49b97c4024145eb477711ab5bfed4a7544f490db58ff1e32c3ff1b6851b28b583cbc5ee63fb9b445bdec833cd66acd00e3291761010be7fac7cce5eaede4e251379e7d6967cdf2c49bb8390e024ec889ba5da7e61b2e70502ba2ad822bc533c88230fd9378f4e4fed5dff4a2c825023f9afaf27e48194afd3a3a687b8268a0dcf22ebfdae9a5247baed6d95b7298d2874b680fffb2d0163e23fcfcde05f6f7bb4fb65de8ed4a0442777725650ef5d14f35d29f49150dca7c19fcab3253738665e3b82a684dc39285c55defb114b8747fdb0a494ced4075215a81c14561b95ac4d8701bcb8db99027595d732e6a69b3138604416f57e6e4dd7d3ab75df0df2b56f18041ae5f7ea823767ec828dd9f4dd8ddd9ad5697f07d9e24ad38fcc4ddbafaef84ee26e1c20750977360dac941f59d9e5b9ebf2f1f9cc1c5d6e69d5fd958b06e6ea4a1e3eb6b3d60f12efd34e452f0393be2f396a0abc9569fe02e2721a49bb68ffb3698b35977b355e60663c69dea8737f41992f6fd0d447ddc8734b760569009be6d09de67caff09213514febf0a17087fb9a63fa62474aeac666475128f0df9d5a54f50449325af6fea1b129fa922cddddffc15e48e520ef7de678238a5b8dd089b3263c66e9b4a4a99a04b41b593767b3896c8e6ca290bffb6a2ee93a5b9bb097ed0a2d5b62bca0c7ca6d9b08c7fef0039601252457bd712a5663b38c93724a5c108ccb87648f7631c648d91622ffa46b83a92880726d7594b8bd75ed3d5f27f4474c76dc5c59d5966ea551ad50a048836962c317c985b2e9bbd1dc7e9cabc76d82fac6d5515d9ea7777ba71368c7e0316c5426fceb2dc5785afb19107f3c96a4992c4cc702b25814536fdfb705a45cfdc4a64e14f10ff3d4fee062ce6240523001b14cf7797450aae05980fd6f70dc2b77ab8bb83a364a2f018fdb9d3ad8d72077e2d3bf5cd68787d7f22cf3989e05cac007414903b2373c482ef6e7fd69aaab3590142e346426aeadb635d27dc9f007ff523b3e05c6feb35c47b00d6feac8fef39ffb72e232398ae7fe76701a25330c8dede6fb9bc1abef8e06e2fc37dc97474bc4c418b36b0718ebc747ab6550b38f5764fdb8d9e0e94f7ee95c00a101aec6a064af518848e8e8e336fd2c6901a2f6072b13693ded4a6ceedec86ca67a12ee40ba67e513124429f689a24eac2daf30a4b4c059816bcd31aac5e8bba69f47bdae5953e56f5910f8632cf5fc637346c3b8ba78b41fa10f5c7155e99711cda1f2210eeae5d2d6c7c52cc30e49232bd4264446f4c268c8580fb1881536b997f7c047f180e28459146e27d39ec7a11a78f50ccc67489257af13eaf5c9bb9ff656a2b37f4f130bcfe8f7703826793f3d019db197ae7fc4c2f67cc0801a5f07c629d020ac071d34302d48a8b876460211bf21fd938b5d2019c02e86de8b478d180adabd91e52591cd380d4ee0532a249d67543561c873c383a82bb1ca60a54fc26b61b94146217ea3ff3f6b10f590e836942adc7ed9c8490bd2a6bb12ac8ada7399bb32cabdb87ce7339626c1f30e8dff08e68c9f85579db4125ae74604af095dccbf54b5a8d6c3be686cff8554c71fa1000093d352a1e67f825c469d0a82275c5595c62df57a084b6341b1156d0e4619eba093037518abeb91e8c2f6cd111a33fed7f3707bf680753edccf12cbeaeee8b6effb3e0feb8debeb159a1469505b3d2c4fafc128b8083f7d8ef9c188afc858c925dafffbaf95959699b73dbecee3663bbad48cf6481f15e2020ec322029403f73fac46612b25e5cf87e39273b3f677cd6bf83d841e349606317fd5998443f3580c6520ab45d52bfbe435d07dee3cc41ede7be229f1c2feadb4e422446fc4cb069acd83e77ad6e22232eff5327c566b47e89ea1307953019dd1b4871a1558e2ddbc06cc5cba1969afa0c8a9fa6e47fcee49d0f88a2593d54ed770db4dcd04d07147294dbe512b6fc9c6ab8b6cb417a5ac476a043f40dcdf8acc2a87ed2a2c9acbb7898cad63fbf4e75dacb0ba13daaee216b3924c56ae5d0b4df09e02731bfe5cc7782007cb2a953e390fb7944ac99fc9fd53d14cbe359ad3c3c29250e7508a8b502d05bd2161d13ad2b32e3458688844c31d732d27fe280557055b8d32bf2e9e9de1d24f44cc9e7af86c4400b654e48c1a72303b22ee9f5ac1cf413dd83d7cdf142a3600ba3a603abfb70a037b7dc3b8f5ff869b1f8a7894e5c7f6abfc2f2a065461b0c4e5b4fc3f0f726641982287731510fe9dd2dba7d8e2aea0a72501ac367ddfe2623819203c8564d0ee5d3f661c438463d54fd52233a52b3b27efceac28a28a3c819b2545c736a7ea87e02b0ba2f82191a1aeb8c3bdbf29996710cf252d07e6deffc5c209c1d785f22f0b0f9d7382162db17300f3e9d3023a48193531cc22bc8d83ad36b76636dea9280bc326c5107fde116e243a138229301a2b6cc4d99d232724d46d5ac84823a215547c5d80f2790d3d9a41df3c5d8f582d6142e76c836e62132607252e4df4c91e2a4ff66216dd46f6e92d00c2840c9a12ec44fb3739b8f9a698d8f02c4ab2122378fe8415c96c665de53571ca407d0a3bb9e64c8b1475fd9ba52c9a0f9f38967b813d3ca850e21e0159850fb7eab5cbe706b2193c6d2e97f5702cce77295f076a2cb0df3c9e68afe7c088b2ee075037bbe5fb64467ceb707317f165f37979444f5f8a861b5b08d17fa84fac3ecc60b8c5b443ba39171e289ff2aa799dd03adefca82bf3c24d1fa8abf27c01bee1aa370465d64e80d5d0b90555e7cbcfac55f5ee73f60fbb659c12bd5696fc6a2d9f9feb8ee1dc826f46150c79ec02f61656a96c0bc45f8bd6a3be493cf85cb48f37a7920fa15f731c38c7c8eb2aa94bd5ba42f4c34b858cc726adb733252e8133427d55aaf4a877bc7be1967576a141a9483a8ccb73d3b402935df96a877749a54fd5a721fa1382209441496318b7b6bd8b61155ad95307165bdcaaa85271fa7d059908edbd754d004ef5aca827393dc8db5c692bab23b68de7b5c4d038984568e02cb8a0e9795d38892f00025d62a88af01023f5a375aa3b63388c706251fb8f5eb3d7e609e09a7c1598e953911da7ec0090ce828a1a2abcc65cd668c139446152a7de2735f22d5cfacb2fb0b5fe2c8741cab0f4591dbcba65f933ca00850592eca9b8dcc52c5a019aa6923d3a6675b336140a6f6d9ac7333a9a8e86facef762de72910c157dad6aa7038ad28751a8544400d537b16158d4cadeeebf2c0cd0fe389da0c7af0bc3e14d5cd38a5788441487abaf9fbf2e2d1a20f3f91522bfa812a3ec01e258e0c6f57210a6193581af54708b2466bda92eb3d8d926b6ef72c3fb99b752c905b158d47ed0b71040bc1871af3a7f6e59d604072b75ad8e1493a26102c2321635e234c345897e0bb4c13ca1593f6365226280eddea6051f7a7b0d98f6ca8e03615da0f9f7b73b5e5bde06344bae642d59c4f4bb83e4ec5ae3096f5df5e75a49ad297c6eefddf57e601aa06e923ed7a4f2e4941f63fcec854c535caf1c48c6f853163ff42a5b6f2db9efd032562ba853df38791549ce2fde43826e59f0907742b28f25ff952bd24d203672d8c773a3524a864745c44e4a12c37ae83154d40175f117076ed17f38248c5674483d54d787a8634940b77571e2a545bef80afe87554e2273e523f999e32fc377abd995d7e4b91dcdb80cc5ddeb3e23191e9b89348e00901cfce29da528117472f156a75746a63eed0e99c1c8d267644e8f1ca49aaad6899aaa7aad79c869ee61ce45d9a32d6b4da00544526c42c981c9cd4a64e6522d8d0867cf78bb26913e20bb611c735edb754e8ac4c17245ab6070f5115722e6bc65bd32d3af526dd472abdacb9659789c6d8c8ac6abbb00856e55b6c3425cc54a3edbccbedad0892c594e61046ed29bac3df768e42013bf34c2812d2bc3a5a1766430a1943e983b5e0ed218e45bb907595448a3fa09d297aca2056b90d6be2125192826884334996f8ee82f1cf53cf5fcd9fee9090b795e4815293f3cbfbaad5b3ab18f589be32a6d8ca8aa9bea55f9395a48109340863ed59f9591be4f032ea975bde9508360744c10a5348b76572487ad56a167a921dcbc6d9266c32c8146fa4c625530fb8600296adf99286fc5e029d3806559286194596551f2763cb9f7d81e7fb9f1c83e4d93814e68af6019656d1ac701cd60467bcac7fe0e02bf7da02e36d57a56f116b6497b44dbbe806a559be365c989fec5f10f8ac16ea5cfb92b4f6d7deb0b92c5949d0823811a3ae796f430c91efe5f3a081f57843e03fddd8da4c16198a8969076179fdb63de7c4bdec9ff24f01cb1b6e8bba28984e69023dd0914e5b0ed512bd24c7010c428a517d97a5f6bd0168176e2c5aa8aadcaf05462e023f126fd18749f83f0f2abd0efe0d0a5e2f3be766c544d58c40694848d896b0ac034e19281f26eddbc8fe7cf5a844ca803885e3b81fb84871f96688c5e420344f70d12b9c8b4818daa94337d12de1571091d3ebcebd934cab303a6eb54d62e0789802eb3352487d5989730a872d22182fa623db7d462c1deb26f4125a6980c63fbb4779f5c87ee3c3763e427d31988f03640780a32d62caade40fcded7a2f3447fd4750617a1d988d160efa1a47abf0082db3b0fb70c50777a86334998382b55f16e2c102d2fe862019e6b16beb95866da282db38e83a88b9645e188a106c0a97087f0e2e497d53a22b65b6b2e9b18cd90213f52e150520480d5d3d9679ae7b5f8bb842ac273243ff7ffc0b22911ea2293c4ceb61295b69faada48792675cfb904f02c7012482d949911bd6e9690469e4de04fa07877febe843a4d99e934cd2e9796a17f7a805aacaf09559af91a618b8a8a8086b1da3467910110b996a2a95592a21323e9a4e1db59cc513cb33cc5565d45e1153ff74f959959271adbc50beabad2b63382ab6294e148280e83462019e65c20bafb24b1523b7702e1b64a7704c45231839743c9497a7b89785c903c34297d950943fd5840ce98452221da0a641a1c5b6e65cfd3c9535821832b15c086c4fbf941e500ad6b5807b8439625ebced39c847b44ec91812bbc236e69ec64c3d03a755e9955f131258ee39308c6ac632d8f62cd0d6429ac6c2d1b626d831d3dc29db4da7a2b7900ee8187728a4e1b0783e12f72707d524061d8a037e79dff468de5fc256a920ac057d0f7edeb685b8aeacdcb6c514f722f43ec860f151e1d3f8a1155fb1582cf6cc92e5da5ffec5ca2bea2e54559ea8b561f4bbd5681e546176e4e5ab19cb252d592e15d5df183a16bf8618f2d27d7bc055d2690e0fe115a52babfef80db99f268da314b33f7cb6340c1d4f74f764e0eff708084140abe05a7d56aa5232bc4e038f7ea0adeb7a127f916c54c9d111cd4e5709e5d4adc1bd622d20a902c7399bd2ba806ab7a0ccdb3af29c2d92241bd118d2b64c3ddf2c6d25400726ce8a30de690020ba4f76ecc8933ac392e61bdd2987475de0eed1905b328e6c56f5facddfa01414a5f40350b47ba4ed9a6e50e97bd432232c5b7a0d74ce1b7142328ae25a90dc869b486eeef0c72751c5772351bcb0a575d22989fb5d8eb3fd572780446fc84317c1bb6ba55b10e74f30cc9b63cbecee1def9eb564659040c64ee68b12a2d8cae584985221ce7cb8fbb031acbffe5ecf30e2a56d2b9f162a890fb881fb1cab649c53246fc080581753377f6853cd1eed59a3c659107496f600321d2415ac88dfd1ffdb63007ef5b4f32b2941603026e5fa13d8f35457910a3770310ddf93eb201b747b96a8dfe9ae0b0f9c6c6a1f71d086580b36678a628cd270bf35f68bbce69fef9af7321c1ddf574507520b6bcb48f5ff1195f131cddb3c584708a911e5895b073e54d85a136f2a304a666c5572ec12c759e3edf41b93fcf566cbc5d25411d42cb1f7cf0ec9b7a773050f2f49e3943d30275200126000eac482de601cc147d52e48fa2fa47fa157ee09f1693bdcb46b38d8d9c740e0af6da792e2ee991968408a0563b76db5985e12e44bb0edc57e42ef2b482a51eb4de06b15a4dc126144e24bedf5d32eec8fa28cf787157b9293a56693379a266a1966d913c0d731ae79a504e9b2edb18d6da9188d3cf8183dcce151747df8074bfcfdeaefac62d703c9ba30453a3481217b920ca87fbb08a5c206c89d374ef8c8eb9770420b2817d695489f6573ef23ce2c1b2f59d000f2eb38dd82b4e52b5c973aa67d2ba205bf8e393a96caa50d9055ed1f20be0e63c73ff00646fd743009530a0c3620ba620e353d6b9ae52f168119f87a8d2df061b89dbc54edf3ba1436aaa50d8c5593dbbecb8793a431b5911a28715330e009aa1cde353f84459c648cb7623d58661b6eea68a156e59c455ec2f84d181200f2b529b76cefd2a653b479db8f75ab8a978b122dedccca8cf9b69d365a697d69881df537a142dbfc81eb82d98cf09f6b4508788ad3e93e5395d10e3ada6f3f4969a6f3d1cdfc24435671242d9526572ee258abf7da380aecff25207119565ba1b8ce7292e9ae03ce2dc034a4d52867e81a1eb2b5a48e831b8e563067e4240ffe7db46c7c58cb571483db4350e663d465c00f58500f770b1cdba888ec64a43dfa73974b87e0cea1c43832fde9bb84be2cde2b2f7df679e2591b48a8cd3ce5bcb50ef15b6a65d4b7929907a49fb127853ba16b7aedd840587eea478075ce6a57965429643060f1167c9eaed6620d61ff67f0f23e6b7413415059d81fe9296d8f4451ff25ca4ea42e9e490bc71273d2e78cf0926bbe4b7eda07e4fa33de99daa453b2e67c11bc0e29236ad6eee29beeac82e0939dbe9be1bfbd0b0214032a090a4a1006c017c3ce70c644d15513c1eda936c71e47225100fd682cd5160ec60fc1a34e9c2cc9948e02a182d024e270ae7e42a633147a6af80b13d9b8017a01875610a3e53918f8ec8e7ae440d6346c7fdfc04ec30a90d057d078e5119614e11fb4c0b748ff4203868502de5ff094d525cb59a038f18b50c9503400378572b3cd8f13fd62ea26e22390e75495c1ffc21674fba9d1fcdbc66c774e0ac9d691c85e8f858ebd49a3ce23a5be79764ba2670c20221ff60a0abdfe8e2178c78ea88fa88925b3ecd09e04ddf8cfcfd2ef2a2d3cf526ee1d75b85574e79c2c5933c8103c1d74b411484b8ac777e6daa40f6cfa58e422a2d938fca4b1afc7689971e2592c56957bfd97056adb4284449a161f6b3954016f7d037fd9da085ba992381220d142b4eaec383ec2d19b0e58d6929b54b7c945085d8dec56344783986a0d029c6bae6101ad8d08af5ac1f6dff4427d4554293b77f227517e8359b9f83e8efd30aafc1317be49b915f27b156d112021a3ae74a64c9946d333b0d9d5307fcdea1a702a92e89e08a102e02e65b4cec861663388a2927125460f231cee970cb747edcbef4752de07f1ec0004c60e8f8bc257df9ee9ad7647189ca6d439f27394b95bd8166bdcfb64ded34312a4637af3f70f2d1ed4d6c5e8dc06a76c6e6412333d1c92009e6cc36a3b6ee5c9f6b358d2a1fd92773b22905d6553782d55ea023ac22bac0840b87bd8e70d6264a8ac703b85c77449a3e9ffb71c05e30af27151aee8daa1de452b354c06fd75564bdd54b331ae5077bda051ab6fc625ed53346545ae431da3cbb49deb057835f4cf31c49e7d8df669013f182b380f71912ca6467ce7bc385d2e88632af953ed7a3edbecbe8b004f74fe608468df0238fab321a983c208a7e6d436515d0359f54433d24f32e7bcf0281939e7272f87ed352d8d9d48bcc61230fa68d0d540b9ea493f43fc5027a94ff0ba260ba8b66ecfdef5584359ff98f0812c0fc3a4ef6a968369b84d78a5923af908cb0a15658fd050432ad53ea5d22fd3ffb509573d82f4ba94775a20894a26e3a1d541c1f921450bcf1870735ac1faa5309f40dfa6f1c65c8f5494301007ccb276c1d189171f90f0203d49cd741ee7be505eafdee5d3c74096121a253c83f0821e80da2b3056c0bcb8d8114af73618b7fe6f618d6f5a70a137ff6a8b985a3d09e56ef219805856ed6dfd4046bce26078464f7411a02a2cf3e80466e2f64e4cbfce86040bb41e15bd2b49e8bc1e258d835f56ac74429934b2931e1136c6052dab5ceb4d89701a8e8bcc7c49d7d3b6356f97543397a306e6cc99294ae352db7970c0749604bff68b413014252b805d7a1d4828d2a0861e7aab1701c7855e8d0c23628c411bb570698c37f13cd53e4c735eba34f3319528e15c039f8024084be3b8aa92d69f8e1f97187b7578584deeeb227025ba6c0a68a8c7079221c67e94866747b6b858bd0c8f3267521af167eaeb2b1d695c285c4405bd5ab38e3d853a233c1ce1e8aa7decc81cd2e47e03db3aa801e6de68fc83a85dc6d88d10ac0f048e0d4fbeb957548a55cc79d71255f99dc479c0667616e25cd41c8e536b87f3703f50643188dbba5f266c9354883594f70c0e59f51f711c9018f8acd4fdabe7981c3715f0f4ac9e157fa92bf0b76f28af157abd11f5bebb6ab03469789be8f01f6b586940886d83e9156e306ae28fd3c673bdc46eef63483afe4298fbef80ffda5f7768b9d3908bc1bd26bb166631a0a886aa615281e4731debbefd1eb8344b80e8ff5513c9c5fd8302c0f705d58ccde927e8ee0a50508890261df5da007d207345b8ba16f1610ebecd2fc7511b67ea09c5612be71254d1f8d18d870398e361d450a15b2c73c9185eba098db96bed67f0029c593c7696345ac9d40f8362c1abf13f85582d8d3ecdd171a1fa6e0ceb5118047199ffa46ef65d0047d54a40aa43e31e1de6f122c77339750c860c82178e7b4a91e1609ac196536e5c4a7ffe5730d3d2fe86790b2d213d6e3bd5545647ce0f73a8459844c481867cd3a412b6d8278d7e487b6d17d404a7b3a5ea60249705435bf876cf148e4daa23821bef9b27da120ae32efdf33aa3f9edb04e3f2d2e15233254cb0b49ec10b3ca7f1296d006e457f5a7cdccae5575687bd3dddbf98263f84f7edda4bbbadb7d48a5b0b5ea74fa10e9032e860189ebd326b0e1f1ab7e5dcd7829a6ff6243c82c8ea31916eb5fc76712f2760fafe4cec86725337fad41e6c8d478781fa85e458ccf0a0a744081d11ceef856355c379a926b78570d965e5d3090b71aabeb532215115d4eba87d2a30205d53ddc16e5d4d940d12c87040159253c5b709264d72930b391b969ef6eade8b95dcf6c7bb7339daf2ee1a79ba6a1400457d79b35050b0e91abe95684f50fe0e2f8e41f6996493dc38113a363e6c08c8686447708c30578aeafca024f16855d818670dfb69b0574ff8e73ca654b1dd043a32ee397d4b0435e2d2f4ed05a76df24e892a05f414dd220eff37dd1643e7e59381759f61f91e876b3baf71162f98bedaefd679df3eaa8b236b0b32d8900c97844bd55808534a87dbe5ba144bb10f7f860f0bdab5902d40fcda05652c7fbaf60a683706f7871f7e6f8194bb8ad4ba7fe94720d79a0457af9ff013af7b10d46898ca2988c4aba48bf51f59c7700b14c3565e96cdf7fb4f148959fd08a460716fc7f2b82647f3a50983292b66bb2b1f8abc6b919c15df133a5a56fc1af2246dfe639c8be5a35630367a87d020f6af1295f4abe714e7d0b1d0f31917ac48bfd8f487d8a63c7d80be7282779e4f3e7141313cb1275fbe95ceadd4589ff214fd5ed56fe0b603ebce5a6168124d8363291be7b40cb00ddc5111a145c9b1c300653d7edfebd6a849334185f20e7df68924b9dc91211548c781e6b2410b73aae601c9dd81d33f6a9ede8b9b3fb61ef32a72a4d54b06ddf74e16a896b729df6c8c97fc85e789e38db903a2c5c58bf69bb957487e9164c283b3594dddd29f1db8ffd7ba79e1b37d6ebe4bae1be5a966b62c4731b231b17a3695247f8e09d791eb072723b0b51dbba7fc49b623356c1ee918cf4a4f66a96156da80e0d2f4c0542a637387ed730dcd7f962b864b935ffbf69a3bc338a5da6c59d4b0e61cda762f345b43971638b89129c6c4662fcb281c8e4d63de0e086a3c62dec5a4b656b14949729e946980b439447639b4f3a608d249a319fbddb46941e6aa248ec87c129049ebf5f24ab0f1f91617bcc55328da7c5d158c9cfebdf2c7200d2fb673558369dfe819e27529262e2a49a8783ac11911df2c7249e35ebfc35541be585fbc4f2b0a58bcfd7046c6972c90503ce65c56bef8e2014310ce958f33ae0cbdd3cfd1ab73fe913e34f26d05e867736158c17c1ae365e44bc4caed590ce73df67c664a2a1a8c363d31598c5180594210405a4ee206db50f1fbce32fa3249ab302b65fe28d0cedc3ced0aa40eb8b3ea91b663fe4e179bf781f66af4ab752523a64fb7fddeec8987636dc1434a9f60abccbf7de0c84ac72c4ffd2e90e47b2d4ebdda3454edf3c7bbfb27aab3fcd7c362678f7798c2b4726605bb8216b5357c79f10ddcf112284739b8bf82422b0f1c2592aca84040d37e3cc8417983657d51fc27f112d7ea13584fcdc3a0764fff8c9d3fe71e61d2e2c898e0242b82e335f3bf5527c300705ab6404645510328b6be0aa42d427f04a31441a8f2d69a51818fc734593ed1c3ded9bf9ccce621602545a52c92bbde0fd8298eaf9a6c5227049836073125d0f8205e3020c842a742944208968490a2640ff3cd535bce1b74240e99031f8308111005026e075707e7d352cb7a2a174b6beac0019ee6ebc105f0d7def37828da8c24457f923090444b5c3e0e81867da86644b7aa755ce76b145bc9c8c34683c2a28701982eb22b7b47088dc04aea61f810771208a65085721255e8b513a483b409ad2c9fcd6b128331058f638c840f85e936dc907c2d3d32879ee39496800ceb84a5d94c99797ceb614677c7e1cae6b991ded095985ba4f133ae1b210799349755d45591a1d2abc023eb4410644374e39405cd3f1db8de9d5b2179987926071399f9fd5a86dd871f9ed86155006abaab315cbea4aabb868ff26b762c7a5c39cd82fb3d493c2219a9fc146e20b0238376c741621c40e426c9c47446df718e6f7674fbb5e0eb73d6c9e59aa60d1204dee39450aa26d808bfbd8e0a52e63673172b7b1cbdc6ad55bb3dbca3d8f3c6e811708812324f632fbf96eca28afe543e20bb8a27dba965ba88027935d8525726243260532d58edecaf42a4911aa5213453f5ffe4d4b88d9e275ca97d720b6f1e86f8e2b3916cb24db6b89dd58b24a7b490c8a66f6f1c2a91e0aa32fae61caa63a1da1254730725c03157bfe2ba00b0844f118e5f2da9c894726b50cfd9faa8dd52af7fafd2bb945d38a4c5016e5b8e904b1b50ad0549e907b28483c5a2ad365cd722f3cc153a95a6c9ea969370857bcf18f431803a890c1e3391302b5302d994ddc9aa3bfb4a5e7a6a339bb501e78e98490b210f4afc93948dacd68e7eaac0d32e64cf3f72e13c46467a7de990f15bf65ff1d356fc80b977619fc2fd15f523974a0a0000229cec8fdf3f3d1035760a349b1ab28c344c9c9e46b466ee78d5f769b8dfa8747d793616512edad4a7b00707d9f511f4a7845cb6f3fff8cf6ba3c0ac842361b41e43d0cb8f43c173af32d4ada50f73ed91e37ff099a3d8c40d40fb646d5c654c713c485b165bf3ac23ad416fdd64f1b60a722bf93af4e192815b2f3483f179a16bbeb9f20aa8dced6198854e65ea9f834b9abebe46c63025766b88e071334ca6723c76ed932f74af49562db51a80e6d604645dcb8b109e606cddab69a349de5103b8f6cd63e9d4d3eeff21a90929c24c491a822737c53fe4c9eaff8c140f67cb4a823c2bf940239ce312561059aa55bc76123e4a2884b42d848ca07563062605e7a47be1f2d49ef4e82e940b1b348e592a0fdc5823ff7f4c5498da60852eb512d08d39ec2b2d71f3687578bc271841be76cedad0a4aef61a11f9dc02a9fa227e58f35949171c65917e15ce8324cac6b72e46d72e02dde898ef0b1882cd30002993550db948fcaefa88810f00fd27cb621fd4b985b4d8956a6719d92f5ee6749d23e599fd890209406071de4341953259b57a5c997b7c60944f766bff31d3984884d74c2b655e59d28a07c0a1239a924f617af64d0997e85a5a1bfcf383430bfca6d79e7f4a6359dcd42e277a156ac0521ea5f41e8483ea89cc2485df13e3b37c7892668680320bb5750a640c7b8e10420b5171c132f3b6ab7c997768684df6be2686da25f43e8cb341fabfb39c6bc716d4313a834da1e93929f85636efe32eb2518f8ae2282b53fc211765de5cde97fd3331ac41b1fbad3cf52bcbcdb7bbfe9f4f8e0852a18ed6c0dadcf1fe3552ed7f9fbd4e200e9b1f6c70bd7e63f89e8d7ddd5f91522ae27ab0539d49789bbebe4c9a084cd951c875c713250fa57f5efb3f007998f58656374b0ba20ef7adfc2a476303fd6af2e1df372c19089ec5379fdd904ed457a391c6fcb278d839d08e36a2a0450cf716c2831e210d7f3ecc37819b26b739f68e5267d654daa524bf713e7a48e593f369d7977c327bc9a91f0f2fa709f08dd7b3bc49b5214faf81804659049fe0224da91d5202ca5422daa8dfd16be034b65db627d865df1dbf31cc4bb0766ec4ee12b956d95b7a57b2066e6edae75fd459fc554c6f6b8d6bb8d0b8b9cf6884e716b6b6178ab4d71a4d8f9166373fb728fc7a792b50d7ba7b5eed89ea7c2309b60606a6937c54795d1d7d1b507ca4e162c10969116e436f2aa0c457782560f9eaa2bd733e8f3cf1b0673ac9443fdc4a10f9042787bfe9db193ab642f77ae2ca0fdf913c90fcfbce4ba25242e83868d2e6e29bb9c4de9388601ee2f3c54b9fc312a537204465e1995b8caac53cf97141f4abf20fc3620f7df0263ee22475c864e09e9c2c24ed893072b2c87e0eed6fab053452231fc1d0735ca060b6cb5eca9590b91b6943df5c276fb6095b03a63a613fe04fbfeafbf9bf329c5683744884ee058a4ddb232276a5e9fa82a9a47eb18166b3b749366fb6c4dfb6a47d3ceaa787219a3708bf2e262e10fe9f5a931666cc785c39e2110dc7e956c13ffd7ced6acc841bb38e0bf0ed551087f89f98e8b0378e5b874db8b5864cf1e06bd31522331a842ed5b50b4612858e5085c6d850d5a461173b7e6bc55d7e36e6a2fb8d5bc6f3bd49d91f1e64dd31ac395d8962fd69d9211607aff700aa48d8b476b0a1f4fee46d75cf9be8ce94cb00f5008b506e54a3d60eccc771bc82d1ce4dd297da7497e643f0d2580c3d341395248ee77d51472762582b4bbee3c03f104bd5e2cb171790e8fbfc8216548e5ee85c59b2dc0c2a46c6729e388d55d004d450c10745786322ff0b86e746a60d71f7f7e0985e1b121f52044e0ff77c7fdc1db571ba47bbde74eab679b28c2ad23311a6b1c3f781eaaac2b8b569a361150a0b859445ed89084c92015de980a0f527d88842c91f4e27797df87737d1d0df6235359dfdfe4bc1e042ab5b31d5e5decd387efa346603e07eeefa3d350cdeb411816139e358a00a3772755e862335fe9587fed40f0097f4452fda8a607bce60df0e047143860f21ddb482ce0d256bd3bc9f2a845237b78710f6b172eb35cd815daddcc6425c9d3fb1005b4f838d7a400a137e6c1543bd96195527ac44f059dced9974e61ab0fbc2f809110b134898e42a5458fdbbf31cb98bd3d0a21305bafcab0235b6d252358a58e06885d19f56154a9d80966b1103df432089cd7a100c0949321824bad6d7d540a8d66dfbef77396334f26ab9fc384f6087a6a4d86056025d075a7df6c6b4764419ccbaa57e1037bae270d761a6065485076a90be3fd5cbdec21d2b2b321fd484f3787724bf849bad06d8436ef1f33718361b1668ccac811d8282f9b72ceb564b47f4144ecc2eea2db6d9ae1bde3948f36edef68a509577cd77efcdbe72c90eb474415a3b16801dae4bba89ced483871c2e61cf8791e5e438165a24fff81c6a9785bb5c450d86a8deb6c3f023c6462e6b13c12051a3796ee01fa53c1dd7614a61296c4bcc00beff519d857768a3615a008292600f7abbb046166cfdedaf7daab86e80278b1b3dc7ec52b01249680f1f60e4727260bece50614109544c76f59b37c7ce42663c2db8ace32a8a170156804182d0b9bc27830afa90a3d0a4aefb0dff912fa624d155005e39e05f549519de0b760706bd9f8ad91704f36690a39979a2fc602ee9e0741b044df1c0c6ac6fd520b74c4b542507a9749981e40763f9763c90623a9b0139eddd92f58c6137331327db2489e78070646d5fdfe648d204b3bab3729b24b2b190aa5f5e4c9c1fd2715a2abd9fa5ecb3a19bedff19c6d01bdc30d6b21809bb9dd7f593b80660ea5fcd924ceee28a4f062b79d2d78a5cb06bb3cd819d92e9607b7ebab9f6162c06d233840af0aced1f43db021d09391b760cb1eb4e103ed4b12140068d93af395963de9e6a74738d1d1581b6f9c87cc1470bb4b36aa953f3a9b93b8c0c550275e4911ee4008abdc77430ea541dcfbb3cbfeeaaddc3b430aaf73b881c1a3417f6b2dee0313da27d560e6b34519f0e0ac3aa4db6da3195cd6eff7a56429a19b7d52c54affd32e9620d9e95ee35aa63542e37c12348fb7219faedea1abac675a5b3e38ca7c15f6fa5c237e94241c5efb3a0efbcb94085f5d6dd0b5855a673daf712bb953cb13581a060623ab53a5ce9ca5d98f528cb63888bd9132ca5346c9ef7ac9622d383a33bc5ead73fd44bb329b3079d62fb5bf8df1409aef6f9f335c47387b5723745af32c448419fa16dab827856c3819b1328196cc5e0088bda8e64ec9aa85140e93fc77175a9374db4f45d4bacdd9e4c752d077cfba2f8b59a662ae70c78f3c77c281128d18aa8f12cd89cb2a9fa91bcaea1ef08c4ba98fcd769f46b63b17e788edf3ca4349f00b2cac9a6decb93fdc7f1f34980c1ebef413d333b47ae5d549045895d16e272182e0657ab7e416c2cf47dd1c9f54d83b8b773830d7822bddd562a8d7b68e41fac5e9077fa09231009ac9e2255e61f8561a3506f2b9725878d1b4f4d48368ed640ba44ccf3100da75c8415bbdae66fa0a5ac4bdce92fa789fac6b35ce7d2165b83322848ddc5d136d3ea79c071a51120658e2950adb146f3d61a7c9482f18b202942833acdff41257bae45034cccfc96281372102c1ce2afbad4e8ba550fc0458751601c0a8c5183500c3198a77d18c01b6e89d1e0baccd9ca42a16f4fd3c15bdc0b5e51b9b502393e75420d8deb2032363b91ddf0935e1ef3ee1f4c558b2c5bad629fe3022dd283048463999d392d49d8d78c0bcc07d15c0f1f1f674102b5de36aa77081ee28e84b0db281ac8555ef894336bbe4c8fe0046f3a5adf1683a657761a44dacc3ed6e8d05bab463c32a22383729db85508beb49b0b7ee018baf54e0e35026c53fa9097edca938021de03c8c2e6b669910ad3c3cf03a0a373c3990fa07c4e53343d055df27861d60a3a1ca70b5eec0b13f8884e86ec9e9f946ba659f960155230405b76c17c4abc5fb08b60c7af2c6a24dad04ff8695fe7317869baef0ac5d42eed19741a4518d05734fb65ee815d9b38fa94358b3016dc76cf1fa367e12360ae8da788af64cfdcb19240d3eefced5f84e53f5050a7d5111abda7cb00ae24c0c6b5b69c05c59f704800cfadd2e32e1bec164e47000a31b783bb89410441d9e0eccf882ce1c4cc84554c58f9b405c849a4d2d74f77ec5b97ad1a207b069bb7a63b81cf7b43b4816b8f73704fd20c4c3b9fa7dc8c3d59b0163784b51d26b95d04cfb70c5574b0381ba36829ba46595beca57b0b7642b861673457f1f1b818c7d25a93d5c0ac3efece1f71b4d551676366b6a10a3bee12d85a0a4b82d94bc7da079e6946078ca8be61315e08911ede9a8b0aa9190935fe905fe9a1b691e5dae575584ae4e0573717883ee6fb76cae8951cd569cf323257266d686e1c4cc2389a1e4d86e18e010cca0088f3fe7f8f2bbf25c5c6c02ca074042e91ee01a5e966b2c5681ca847912e7ea6887bd68aff033548c4910f0cb748bd17bd4236d167f7635c3ae329a1ce93b65b26576f624d1fea764c964cb2c6e02f7fc30ae1e24de8c96d1363fea4b30648d009755141c48261c25b42471252aed22f96a214aac3046d35542889d93ae03ac143edf9bdede6bc9a0aac50c7c95bbff83a394ef5046f708ef4c143150151e7affd365ac28e4c6eea815d52cecd1cac8c903c7661bb69ebb815e621023efe8e67ebb5a6dce326a1bb610d8cc7418b27311209fc9380117527586305a1d03ba250290da765f9cb77b40da2c39e1f2797e3ff1d11098dc7b9e9b65b5bcf6000b23377330c040d8f7968608ab9bb32e28122f26733e34057fdd6358fd2415d4269031d769f1c2bd35b135d0a9be01f9e741556690e7c43a7ed164483670e81bea7713fa80e94481fae0a43f469402799e7733c65101ddfde8a8b3f7dfe18a8d8af547dcfd7ed31ad8294fc1e7e353a5f0b8fe95da47f2ffe0d2d2fea03c46501f99858ef564268ba1e800f1fe31a6525f661fbff55f9f594bbdeb0334624dee532eed7872f7e27b34edfd1b9ecec1c1096c2e2843a4df186af03808d942feb41d35792c9945418c3b20278fc0638ca7f953321975a77b6868fffb59c14c690eccf498491c24a20c3850c95f57c68718f8e43ce53399ad33076af21b4f30ccfc782cf5c88fbe1c9bf550988e4564ea923b62ff3c0cc4a4dbb7ccadfa766d7604cec8174c77e72a3e1e9f61c3e5f33fc37c1c2d1dc914b7df880a7470515977c0a4816db66b16d7e209d89c0bf394855ec0d4ff594c8682f5d5b9d004411a0ff93c52357452effc983150b6aca36497dd28f5314ae6b706dccfeb7e243219fc0163b11cab00df1a71f6e555effb2acdb0d9eb0568a4969554919ae94f855522155ff03ede84d83539c152ee4c573982d8feaa9c82256f7f03fbc552c4b9f5e1ee9913f263280355a80a69024a2287bce32a30e53ccdc1d715a516050500ccd6a3c4866f9262c0627af4ff4d4fbde329e7201842144b28af481e8eb7c7bf422f7ba32f6c91770ff491ca2d40d9ba21b7e38ca3a696ea8faa78b684f704776a272011615241c6fe09f61a2239760a706dd7ab73b166c9766a349fd34709def2377f94c673968bb407555f5768382c5e2315445eb5dacbe51c5956151ea0746d2a50a5fa352f1ae0560a1f0b172a29b1ad5dd47da886c7d73b9690499c04d6ba9ba921d666ef7c752bdfe807aef752efce204e3ed18654fb916d9cca401c4cb23b5723a13baf531bc0176458df3a66afd572cdd10f5b6b4796ff2e49f8ec3cb6403349b1edf510cf1b692b0ef8ade0d59ddea12d8e63a1d5edbe4609961bee411e614f7da1f38676d6309a66e4d32efaa0a516944de0caa68bd0257ae762e56acab3f6e9d64f1ebf03f4da1052302540ff15c5b1eeadfa76b562412f59bf1771d1c520ae97ee16bd71dfba26638bdbbe6b6512df52dd5c88599558bf1d76233dd980a1b406e4fdf66b6408352cf72acd5206d86e376ea8a0f8b925731ca8e5cfa8fa76bfe27006bf203dbb8e462945bbc011b41dd32edcbd84f35b676d25bc5c23570d0d89f6206cba03a6a7e0f91838038c63a48d43633793cdd3c283a6e3d1c466ecdf0db6582ca09c0cffc4f2be556a281849a75a6bc25ef62b2cb42d55e7bda553ed638b786539bfe9ad2da16b5d3c8193e5d950b217811f6a6d644ccbbacc3e403415fa9b51926e8304601f27a798622dedd39279f8c3c9c0f9307ce58480a94b5fdf8428c346d295e424bb5a3424b2e174caa8318d3e70fb7877a0e029e81433ec0ea78fa1a2f757b24d490d8a3ceaeee02bc0d6ffd85e1f8d792e9ad08763e3426363d68cb8465896f36307f7e390868ca9ded65aac2f7230d2454248b955267f0584620ba2c5cd927ad007090d8bf7dfe52ae4e5159a78f7b4b37275f1fa247fb988b7080781bfe8552acf0a11d25f24a8f0cca3fbcbee3651a440f2c9999c6b3356f7bf34fb27b484b5691cfcca393d902d5c3ce1cb225405c7f43470c807e7de6c7e301d0da48b66e5ee395f27c53b4ab0e8882126fdd89433ba91dba59d3cb83ff6e7e368f39357f3cd4fd4f1be4110c9e3f15f63ee70309ee9c15d0e1f590b94fb05e658b5653b8c39d90ce2d6bede6eb06303c92a39fdd7f4f2810981c92fbc0dfa97b026f6597cb16234d776717e45803b6a679ca03815cde13c76753c1bff7efd32b506222b76b7a9c31407fd3badaace5f4d56be9dfdf3e12ac899b58ce035b4ed6fab65b19f1da8c309271fd7194a490afd8f8f8deec7a620a2b2f37de723485f5a9201d14cf7004c76ebad4e55ec5b027bc7ef73b0686f5dcf22d05a62f3c51824f53372d6cd895248c294765526d0661456f8d8f1cc1520316b8f3618f9dce69ce296e7c8eb4477a16b2c9cd8be713dad454076df8487d6dd122cb1ddf48c57f7bd04eb1ffa49fe73c482f598c7e2406cca65b29269abe8032979ed29778a49ab2eef6640d5c52350123003b31733bb44773706ef9cd9f59b8859032605b772bc5dcd1b05957c51dcd2dba2dfbd7e2ac8280c1228b67cfe38e464156a4d0e803cd8e10307ec77c7da061975696d358d22a1122eb885b6b3576c500ebe8dc062ee1ced98e5263d0428252d09ebac23225bf82f6343d7de2d9ba741e5cf738b006a4837a0fbede8abe42e0e43665c18551debe65f18277d2b8802833bfe707ce40a173369b38723619f1b15c7329a0fa856366ed624dcf4db8fa4a2aa65af26e32b46f239a5c18bb663bc3eec2b7dfc53e82bc3a210d88e896474e4f1d49f294cc23d7969bf5676785680647a6d185cf5e26814c515459d0941f92dc9814d55ffb739831a732668a2f4a68896e68a64dcee658969f4a0c67414854692bb04fbf1fb858db3ff078f13be3ef18d6db6f3f72d1e74bb3c468200539d8d4940064bf6cd6413a9c59257dba6636445b8473c37fb48f8e41e754f2ed8756fae0b8c34d90eede1d91a6ae1a15330acb929ebdaa91b656c6dffc304500bdd207ec95ad88959ef7eda3d3bcad68f4feee51a8a9070b85ec1189002d5739f65af054408188c9f922bf348ea4ab5556906758a06b94cc22eb54592617f57e4a8966b222de8fb3620d4d42942e54b6a0b63ed3fbe1d09f2daaa74b1b1b73843586bf9d602ed9e5ac4f3214704a0a3788bd5dcf3a060b6f7ed8da23592c9455670e7f696048d37dc75fc272b17537914e747bc20e4878c7eab6de8b066cda5e1ffe8529b88ff5f4c9896ec3c83c19da30cbae014295e404ef5e67762711e2cf866af214b8ef6587f1c47903b4a36c6477de902b9e81e9cab1a9479a563273b2725a5cccdce845841292ad147462d66ab08040af00646d62115c2917c836213d67a0ccd138123cab7c6e3a8d4feec84120cf4b6d11a401d41af81a2de00d83fbaa1914217fb06b719d3e8cc69f1306da54ffd5c90d64aae75103a30a8d472162d042ab7766637cff63ee621bd883b8eb573153b4d85fffc24db03811fdd475c612b7dcf60965dabc7cceb7b894b3db7b2eac22c6b91eb32d6fbb52c9141cb35700dd296c9c01ed99c1561a32e4e159bfcd63ce1808e165b179340925bfc08d9067a6cdcb5323992a988d9ab97d33cd09fb98500e901db160ae18b67c305040d01d93700e4ba7906f44030878a600ddb2f78bc497fdf6cc92ba7534b96298d5f8eb6f8d19b649b6c5d43c0e8bb8b4d2864e86ee5306942436c92fd9043f472eb35f44b367f76581f402a6de5042f4283d57b695be10dd5d12d851591a59fface3330631cec7c43dbe2a0c33b02b36532e0e383f16989ef6e3b884d04fdef760ca27a68e0d916eda6c26a5e5e1cd863ea5cbe92a34967eea1b6617f120e77e94054db887d4a70bd0f7fd34de2a12de99ed8da482d6ec970f388726404da5c465c91b14bb03509210cff04570c549301082202a93ba341a330b929f5a330414759441f328b5ce63f77ba14cb270345b23b27146d2310c81df0654a9b663e1d11238b638a91d1cacb15dcfb0d21362d16df4702bb545f593b6138853fd4c268d261271fb8164935840e8bf97d40c771f0c1771a9d5b9325108b74d0443cb0756d2e49f07bd96150f54b5dff2afa36be8bc8ba1f46e295365f4589a109bffe5cb98f7eceb0a0b165e473b8450e62ca25682b9d072024b5a1cd5806da11377c68f97548732dff49a8a1a283b282fcf2b79fbfeb943bd468f38b8fae1fda9566246a3acf175541d93411e73de7d3841204a89738bbe8c58e422d32493f160d039e40f34a525646aec97b997f20054f69563693f0e907630294909c32ae113cd007976364e6062707674f0a423ce4842de93a4270fd5a94f97f2d1a5ce2da4a75e84004b9bf58fd7f3012c6c39c553d0b7fad4cc4e3b019e59675070b6b56025366202c556c5cb4af2a161460d9765b22c46f53d49c48b55aa35acf74a982dbfb217bbb45504ef60d1068cca64fbdba41c5ddd5e0afd89f1028ca2f34c4ae7af6c78d52eed6b4983ef9e8c68609f5b7837a80cd77c2b317cfd18b90463445ae11ef40feaf123bfbb82ff491a7dcc47e7dc273fb56c892bd40c1091a64bfb2057c5b9ee347a974924b6712a5f932a4364c84accbd2575d56147448bfc06dbaaf37df5efee07102ba7266783172ae0002059fbc08c317f2db7666ecdddbaf8021da4ce7e56734766f1040131c0db6ca841bc567b8358640d38c71f0ec39cc11fa18e30111c3cd2c988ad948773d0171c44543ebd74df936bb7e3867def6540eb2682f480189867eb8d78e84b3a135a18e68f4ae8be32d4cf7964352a773bb92ae3a643fb0e6eac7297a98569038d8aa284955ffe7902426e7ecdc7a873bdb90ea9971eb07e2b81f9fb0a8336a3e76234ccf6c042a2e1399e8d20bec5e4a32000f83632ffade2fdefdd9d7bc7c87d5a375fd0cbac6e222ccaa8757671510861aef92b47ef3be75ebff5e35e2e1f9580c7cc66ff7e6993d30464514384c57063780940e7159ece32b8434a214651bfd1c8dbbcdec699b8faf1805296d542fecabc3a93189246352c0c0d9df0a4fb416112542bbd23d4577dcd7ba3282ce5357c458edc126eafc68ea34496646d808b1c6ddf287105adb0c2f5a6b1fb4779acf8e6788856bef32115893147cd0016bd2d278c782f691065ef16bb2ec74eaab96039b051a16b395ee58b17e2c4da963e3470f319d7b392db72407ab7deaa4b499f0dad941c83431c315914bb610100936b17e678562b64ea94f5556dc04d0d28b816de82dbe3e64019b97935b1890c26ff52954871438dffc585e06b7339c4a37ef7574e3ebc22606574f01f8d4250e17ebecc278886f6f571b1ef7739526bf4743c2da8f69ed71af51d2825c7c91a606e9b85fdfea8ba5c303127c629ed31fb57055685003ea3802a90a21ef003918a11531f380401d5f10d444b665878ad50b59a26c372e1aed572751a5f3704ce55524d100380bd814991d8a755b3bead9b51f2629cc67e2cf5d3372363aeb0a02c45bc271c8ed5982b66fe37971b2e6517a8a436d919270f05ed60a5109562943f34170a8d458bd674dd9370333b8c0e17d8860e8feb3d7e479290e1ada65d25490434f2f9d90ebfb3f8ca0108a1bc24423d6c4f6128601556345aef49b2d04b5fa07bec1a839625e2caa360422e595f0104f102a2ee3ffa7b1f946f487a2c426ee26a96a0a6a6bf2d320f1941b9ccaa3af04f45258c4fd28384e547d9e700e45c99390a6dcbfa930d749be550cafc4ca37cc42f720a2935d631115b51c9847785b4a5ec74ba3306a52d61ad7501a24945bb009513d4705e8ed4393b343b094c0a4ca298a1ab3dede3a3d8ddc371fa96207a65eaf56c7d4f77f38fdf0263880501dc51e9c8375a7e69032055ef8e7e781fee9ac93ca761a52e28f48fdb1a18aae6746b47f4a46581f965b4a3ea2348e9e180140f5b47e025621dfcd3aae0615b46a077abcb7487189fc25ca9405c24295b8b9bbf9e0f39ed51b4803c536b1dac30c2e6f1e4118aa0f47c325a51d189eba61b6b0bd9e111fff706576da4251ad5da7e49f10e27e096c37b2bf000075952aecbd7d3346f1d5ccec653b976e51fd03438f7d2a798b8009104a949015cb31923fa85d1780dc4e881f321acd46fa18dcda62cba1bad82c4eb8ed22c321596a1a0b68e38349f8d62b73c7010d29535106e44e71f70f879e8895c5557048d5bbc66cf50810190a8e3ee9b11c3d078b20171ef92054ee994cf3601f6c0a18a49547c70c35d1102452e123b89cb86818fcbeb25f706683d6c7d1d835db7c02fb36dfd4e7de696d629768534322a3d165cd6a296cbacd342692c81a5baa5bb1f573f44315aed38ab8a3dfdddd00d545f8e5f3810444c06fdcad95000f0aff1d1442bca08394faa26cf7ee7760a7c8fa2976464b47a3374cfbd03ba5edb5d416ffa5ab467335d93952ed8cf80a236910f1090f3eea29b5f1ab5998ef2ec5d97c3ca1cb31fd95dda13b3e339efd66fa659a3be4ad8cfd39a72f988a50109f5dc9fbda1cfdd44e57a9e822c287f377a3694b80d9610771289beec8da1029bb10017a9a94945cf75f08f85455c6272c316764a368d2b486fcbc6d501b55b037e76080cdc12c6225bae026e17b17715b862c686df7d489b8f657763f58516ec9516d895d41eace220ec057b41177fd7e4e9688d175e0167bf62627c610b38d3f5ec2ea056c8918ed69e18da9e97b637038d8cfcccb98b00ec6ba981293aa1ce195e819312086b90a8e7efd6724027d9085d45082c8e836ec54821dedfe6db276d19be3c0647f86679e881f4fb09fcba71a45b6e58c1f007221f3b0edf881b503f81e4012fc44314ee5f6ce9e628c81a4f9a14f62db11de94916811d8275b54c050b237de6ac908ade077af2cea9012d22302d14381d758c4e3bdef6473dbc93b0151c2971d62fedaf0d3aa37e86cc98d6d8c02ad8df0ac612e49ffa098d5017614b426c15be5f110a01b5d3ed6f1320fd709c1b2398b19ca7b4844502dc5733696944180af4bb6315946b362861e90e1f6eb075565abb7116a2cec96b4afade1067e3ecac643e9aff58c22e5657ba77f4b05814b80845679f8546d74af5dbc727b415f30c8ecf776773f04baedeb550be700efbf613d45f4341de5b3e060fdbd7966456b6bce7b82d74fd980e0e0b1052a528b1ed335479a7e8ba8243645364c773574ac4522acd68cf0abb0ff6371274c0f583bcadfecfd5e290e2bf8cd39f0b4338f7cb77055d65358abb4af7e2e466fa5fb89aa3af882c01831c73d0f59823715764792ffca12e4b9d7d650f1a2b1ed2c1a48a7e42a9960deb6ed63633426f7c1b47062bfc7030ebddfa229b530de04baf1c62eb73f0ce857c5ed01ee689275ec977ed85cdf8c27e90cf8511cb83499ac93419169d28a496f7e1f2695e69685dba200f9db4405f3837e4878016f2477eb2bc80292b60e4b849259dbd7dee4ab4475ba9d78a958c0a7b7cfaa972f21cc2b7d651dc734eb43bea0e8dfec809977259ee6f2d2befe38fc81f8fa2a8589158dd7f7f3c9985c765ce7ab43a00c1b291132fd0ac134895ebdedf12e5acbaecfcb2d69ef2d45c557f41645c88b644a5d89fd36ef84f1f526aaa226c368a9c220055613f5bb3d1d13097ae0dbc7ffeb2ec6893dbe586b76dc6602a46043479f956e21cbfbb3f4adb3e1a8ea791e64ac18b0e86aad9437080789be438ea6b031fb4d679c1d786c2ed2bfd3298282d266704cbf0d96cb271cb025c97010f7178389413d042ed9a87a7080fa92068c9f3b5a5d792e5c17a09db44e55e346f8da230e8b59ca49178117d71a9d31fde7c44ed4bea16e9f5e53522b5a00564c982e0f2293252ff2da0c24f9bdfa2c99cb58b6cfb5c2b8b1ee17f3a9d736b797badf17175cafecedf0bec179aed2d7a1e6e8bc88dfe7a980e5327643b6e8b1c88472aa9e2c34f6c12bf472e7cfeeb913719760f4cb3408bffb876da3f1c8aadd3a2fa6ed3a3f1a3851a65321af9f5d747e293745abd1819ace1a0a0f30c132cbe32733d92abb046beedfbb4286724e2454dd015c0079ca3c4cb8146d61708fa85470217d26a0e879578dd40ab47575bc8679ca1c16d2df64e6b517831bb595a8a308a3f2e4f179a1634afb08e049985cbe2f66f8998bc95186f78f05834b38179085c4685006ee677436fe7e112ae7bdd4e53cf696a1d95c29ca8a10147e3654524bf39ad5b12049051fc419103ade8f5f44109f8ea0c2ae28b7488beb47e00d1ef7e4ddc89cd70b2d1ab1027ded9a3d9c8651fc13b7934f4109113f6f94f61b23f4bac53d80c85c19b03c03bf99dd7f903a56febfdc3c6d9c494ef1b779b9e70e603f5be9dbe20fc6b71c3278f6657175b1dd8297ec1e0896215a3c9c33b10b4015e80fc6e7d275874a11bba4c0386bda5b4310c6f70e2da5e0e562e95876734f1b93255f0bccea7d24edec9bd82734675a1c9f97f22f02d55211dd6870be22ff7fd9bbbd7b20a583c034700af3ffc278cefc20be91d6d9fe67c022ce6663eefc6fde578f74a9da995ab92f72591b252a71e962237e047d2a0923d0c2c510070462eb8a2d061ffb37fdeda0b37d26ca9b35e573b153f83fa06ceb2842ce602e7a1bea28560da917de2972a845a1df4e2bfb590030381d7ec27ab0df6583be6ba6cd64e7d3b32dffa7953d42fbe1665ae593a51bb4f6770a54d65d60763d476aeacdf222154225434a0c9f067d71456ccc39a01503137f87b47ed1195d63cb9baaa40f19ce1807a6772ac8d410300f9b0066af06c86cce991d7673b0f61e55595524182f42251bb90276d8cdd8639f0758714504029cf200af7ec12fe80a27de61fd044340dfff5c56a5d6f9ab35e80a417e860650b1ef8223a6ebd1a63dc07795f496d8649696ca684403be6df242482482f7c038f33997a6974d0cb0868eb17ff2b4cbf2c43a9a0b9dab63ece4bd23ffc9d4b48c986850d0d2d7513ef417bb36087a1302582bcaf91ae3f008bcf3c03e838c319b80638776b0fbedd7c699fd97351579871e5aea885f55870bf7b08190915ab4bc6061dc55c80a85e0b038acec23fec4549285942240b3529cf9839963a7bdff31d4288db50825d23b95acdb5f5de30dec8c95da49287c970ceae7c3aa86296b0529e533d5df88f8cef344aade8e424cf3bd8876af92d2a6f92d9880701df004052b54bbb4823fa03d418a32f0c174b47f775446b55e48c45aac526e8c48b4b7cbe909d563cab3c74df38f239bd124b489924409887d62d1fc97cff4fc161c08fe3377c6b20143dbd33f2ac53c9e2e89561333463dfd86a1b3f60c74dde1ed57f9dbbcbf381be40d62ca93407dfda7ba99146afee2864a84c6a5784d038ffa2eefa667b65fc2aa2d5aaace73a2ce3127172b2cb335109c3a702754bb178962e3ce5057a32e121c44cf42b087aa9d5f8641249d447d6624a36d44d8daa334762e0c43494d2175fba5cfc93401dffd1357d3ca6e8cd226e0b240c64a5c99d50c70309e4dd5d2d8f49f9bf31efd5835242962ad594872aaa184261b543ac9b36f2b2b930731981e8bab0157b216f54b66f4048187c108488f15c31ff5c728d66c2857e7c942af28e27040b75544f124027b699c0a249fea3db8b15b1e0db82b73c6a1e5059e4a6cd84f9f19211e5da661d56b3fe549abd8841dca9a3a61ae7cc23881f0346d817b438dbf6c65700257215f5fb3d364e7e6abca29ea7e32aaf8a9fbd6cf7694f617891eff0b6fd182a0ab65c5886cd86f140e2306e6b2926cc4b877c1341a1485a1f98fa3c9f5d2cd4e013ff7c4bfffa708f41b4529660d8d1937d98ec2c87d4822e2cb582aa3dfdeda0fe4b4411d464e81eef643438c55e8a3499f4cd8c5ff95f266f192e60bb17a88f5a87acc023cb7cb95b1ad37b03fcc9454bcbbc817cf5a6e62024255ed7c29a4ad08e5812a673320b0a13b939f6c1d6de5001dc2b74fc9d0c68d4d3dad5668d8bd58d1f57ae0ec3e8d925ca9de7e22452ab9c659657cab88b60770e867ac676d5fde2f559eea327e29e203a66571bb1ac134e457b9e5803a21a13c64b5d79c2b87cc753dcc6806e8c9386d3c32bc74f932bddf08b5373593489091b3cd9dc11276f1b600ed4987f4778efd738bcd17062b6e4ab59d752fc650715b967e5d33c6e69fe3188eafaf3978700b44851c12d3d442884a2dc2d457d75ada4418bd73eddd28855af836cf9a8717432f32ed93e913f785addd988ba84de36982461f00bd4d6c4fc6ae59940d71a36eb7d3c9fe830e79e1a617428b27f11e201e2c81c125f2c55175e9d818521229c1c907103f3672f069960f14b9625c46272f8eb87ece70844dcac2a2aaf7760b9809225560ba6699fa13b8b11c08d2fc3891c6dc07e0e397855c6e31d2371f8cc4fa342ffae4d1d2d9dced56edcc8da9d952afe6c55b2ac9156ec0f57a338e9c3cf12d046dc50741a0f9a562c368cf068c550ed157e7029c19ab66fad0c26cdc1e046f80f552388c8c05858d317563d776b932488266357975c4216d1cfbc371843549531f3b48505418c4733492d1fef7dd1f776030975c0804ded11a133e71a8f0a68ce84634f15605e86a968c07689bb7c7b42321609aa98c764a351f374604f7e973ca09c129863d855097eae0eda3fe7f83e0c8ab4662e6fb182a7628dccff9f0a936de42044ddd6a7d1f490d1ae15a3fa20c3d7920fe98f7577b5e01fd177aab26b2c2515d2713da6acad6a703757fcd3a05c01b45b4cba4d1659ef23f2873986cb95795b6f68908e43d5b4e4f42778e140a2871b8666ef8a2912fa630a87fe3796e8d8c96da1ba76f4d9bc81a1656116df8f1ef1fe297f65f8b88e453d79f827f8c8090baa5abacedd56cd0612489627faa9947946e3a349b8cabff7568db1b4d66301b50315247b164f337b63eb50a24acac053ccf4d2ef13597e29d19d0dade2c1d2b5ee3776c4d040fee3e1d3a11f96c4fe496979c738079ea1c3e7d10f75f3a678c99179468045125bc7a5145a1a1d20994eeadf2bc0cf4ffeb9219f8df1168dda46b23f8944f43c4505edfc7b36dd824ada57424f9fc99931ae5fd085517150635be5ffb40f8a0ff398dc7d81e8f869894983b9a29f16cc9958eb5b5934aeb64be40254a4aae95e1e22bf63d2e73310c8189a4a5085ce21d90cc9c793aa7c0a72d4580b8ed8bef8a91a459c4259b3cc1cd4d7aac7e54553038282a31433ee2733084078f0b701bd242779a07ed7a83782fd7c601becd3d611f70932ca1a5248ae2fa4b178dd3f7abec99e973f3bff7dbbfbdf8d32f25a54b64e390404ec62211685c3e2324128fe4aaa59b867368dca5fc85ff9b11bb55d73a141acfc57c510092983c08fa6cb6a95780c1ff1541d6c2a6625a6cada50df7b99ca24f05f7557455a1db4cf546bc88015a2bd8746e01eb1cc274dc4f8132d7a244984771f7822af403b699692c33409f006034247d17b3ccc9bbd8d95f552e6253e65099db9769b04281eda08827a6dcc96553277919dd6dddd53980c62c5468e90f9be43fc203959236d8b3d7ac51db7c556694915e7647bffc9b97aa132319daa5215eefcecd37449773fbbd5e1734b279953889c615698b12dc6d1ff5696afecac2c3b3b9710ab701f953aa9a099d895ff0e5d407b29468b4c0867e1745622d96fb8b980b860f648afe40c344b7e0622aa77f9259ecc957361dde040ad4f91d322c3c90dbbf42b8195a68c364b3fbf080dcebe9cd80e3b1bac60a23ce02016dc21d3541d8f1293d2dc5b80b308764bba25e2887a17a3a1473bfbb9937d83e66bb8c94f49a1cf9e1fa11d91f93fc9f514404a26dde95999a2c618795efd036854a9f3db050d8f2e6b068c5ed7fbfec58bda7d04b79e5cb229688ea51b18905b49d231d330ed71b805cd2c545c2b36398829db812c35d2a979afe4acf90b95292794434cbb4912e4cb5e6dd254e347cd3d3aa2df07e92ca6a43e0f372088a7ee03ff59b42ca55c4694c84638867a69b52eb389b28ce1285aa079383340b932a87ff0a168676cc9ae79420b004d5c24ec97f9a60c54c756347c7b8c478501b1adf3828604caf3e44a2602de798082eb68070c1a50f2ac46cff1b9ec3d8c8d6f39fd4430f1ce3b71459e0c083f0b8d12e0daa17f996a65154b2d2d86c9f152539b727cfc01355eeec0ed99b4e483cf41142ea96e84899f06191ec841d14407327985fafacb1260598f45506959b4efd81e7a15b19f2cc4c86f00f233fe7f78f90b79ff3865ce05b4da32feb19e8034951b6a502277aa4b48f1f4fbc1c7b0982d0bae952b9462628c829773c01170106a55e8dfd02441bd6d1fc29c07553b01fba43117c0be7aa9a0ccd5a5adad129ee67c73dbee9f390cb8d294ceaf962623a8e6838b91bc539c1564d7924e82607646082ca9cfcad2072a4e3685200ab0c1799fe5b8b9ffbc902d092a8f2a4abdecc4b23e5bf470bb4989023f557487fe3d03442bd1e79317bc8e4c684f2de104cfa29841a609dc07e6c1879424cc291ff5021d00c475cfeefcc4b0db9fb37b4d1e5bc88d0335cbf22e85d26f4b0a0424de14f80370463aaa984b064b943ec3918fd452e3c6eb8fb0165982423223f3ecf68b7091708eace862e5f98dec3715aa3f71b00266f2c981b9e5b6426bdc166b1aa1545c9ac9f6293fc9647894d0b2ce5d99d7afb0baadba74fece7fe19b306fd47c4c5a1dc45d8ccd96a45c345c6cbe216c2a462ca1ba41edafa0c5caf10c961ed7440881f501bd60fef0ddbe73295fc977391185f8068d44240f65cca764d749dc6246cdd043ec3e346154f973c788caa92a7e58f312d43f614b2328701ac28f2a2e5952dd2d67142bed9bece22fa3b47223193b38fdc27b673af61b60e8dd5fae6ebc217ababde03af61d1f3ffb58b492144a6dc912a2bd978854e47b79141e64d1815eb871c3ce75f4b840458da244605eb4df9268bd4d675a6d814b05c35f4b4a6c6aa35c56cc1256103debaadb89b367e916d279936089e504960b8820a89a9d0c2f55d4ea52e1d1b28da73c9b850ab630d23c54d54ef2410dee582a24f50fde5ce432430f1228c278deb0e9bf4f86186f436f66a839fa20868801f1bedd5a27e494e7bedffce5e4264c978fcb093639293d2eaa99106283f11a5f14a970e9849125dfc4307571c89bc74917da69d42f664e99301bc72634349070f1e069dd98fb1289a7a36d8354def5e9f13dd96efea972a4ab53e31904a976284be25f3ffa1935c794ab2782ce469288c4968b2a44e3c784bc2a5040eee2962662241ccae02793a4ee3496ac984f2b7e6317867daac9ad6b8d0315996023826d2953c12f2161764e352460a148227446c9e43e4bfa91d9b6efe86cec5616e02520db5000b335f96234077050125a7423061b83b1b4ba778ede5701c0e16090dcd490548a77c32d7ad56ce4428c9a9252f149994cca3b9801b9e5906aeda975f8c5065bd759ec9e6d4df77d88900754b55c99a0933b694cf52460876ea5ec79d43a82cb3692d714f5ad4fc62cc6ca02983f4fc8de179c15b260307d2ba9f528df0524acce7e4f083cb993e7bf251860ea58feccfb95a41987311a8f599cd02f157713d23137a8e3d03cd52ef9510782385ae467677e115e3b36467ce07c5a83bb72b3e77bfc0cd1b92c60652460880c60d599def99e26d880ba6afcd37c64b5647268215c9300351fa2497b9a68c78f437e4079e94b489814bef0c3fca51276b8fbd57ea038b65201e8dd6c54db2d54e5bf4f6a7ae51c3a0874d096673b835ae389fde23263deeca106f23f25c2e1bd2f13d2c74c00c25c02c7d6436fcdf3b7e29982f1a87782e5774466960cb67046f4df8cd47ed0996cc46899fc21ad6460bcda7fe1a478df4221c1b151c620de3c26718ef07b5b7d3c6eafce7a5d81ef7a95872217fb74dc6bb4c653d2347b9bef97907276196a4e3472a7892e14b8418f5851e101261cd79fb09b12bc2cf4a04b802b99ecb1ef970bcbcb17eecb67c3490cb6020d664b3ad4aa0b69a9e27ea5a97f69fce4cc23216eba9c9c74b28d298063b62ef42ae1709d7088f43afa7dc2b69c20a77e0557783426e73966efef8781072fa9a21abcf72caedadf4a5bfec9aa1320d1347c1cb8e19664c48d5a7d73cc327a9d019f88d2d2c6bae2c643d113ef623dfee078cbeb85dcfbfc2186050d40d0787acd7be062ea14fddf6faccfdb8e598e2b1b65799405b30c2a7083ad1052acebd790eec8e1013ce30d0183c6ef3080838ca1b3bf02a790b16323038de757bb9f3290f55f0f2b86c0708cea6e1ef0a3266c601b83c483e88736aa03b0fd5db53da01024f22d90679aa44753c8038f37644ea6e1bf4190db98b3b1278aeb996684884e10478f6096322b5272bdf32d80e6a298ac61848bfdb4e48b770264937b2d26bb16d70fc323e8fc1294907f4dc9db6f862a23f06b039f27a940437edd7174c3adcce1d5412d2c8aa59218c9b5766ef9ed2d8a33455ebc5dceb06acb5067c612198cbe7d2f3e2e1996bd00a1d602802b964fb81aeed18c9763601ede40619f11f7a5eb0b28a28e7a79ea30c6b19b38aed286a8160982ef7b3199c961ee973907f558963b0092b9e3cd98154f2ff7120b8416053755204c6fc3a21ec14bb880b5efd0c6b19e55f4d2eb113ee408de44cdf69a9157a9d5cb021dec15a55a106d980e56716e27514e4a2c0572792006b637593199ed32a2f6457799e9ccb32e21ecf5beb2e0a502cba4954f813773de5ed3d5fa03501168af8724f6b9dcb02bfca5b909a6e9e972a8ce0d398392eb206ee40e8675c7ceb02be4d83b7982c7fed150392ee130128fd88252ab83c454396a035a0297b2fd42e2b5c04cec90dc366f28a971d431a0a42ee89a4f3529912a4c2d6ed76a796ab5317a8a697190fa5c05d9dcfb28d2198b4e3ef32b14cddb5ccfbe92acec31c1f638776f0fc5dc91fd7309b6bb228e420e31bd6eb7656d2f811bf57da80bbe08f2ba7c4ac4c7289961942f76d901f10e9c9f3ec4ced45192ba933b0993f59bad0c4563343fce4d613f496df24d1c8a416985715f402ed464ee2240b06b858cd1513510385ab9c2914ebd5b7df3b36c608c08a372451e8b4cab96edfb6493daac44bd875dce38f1b8980caa4200fb827c4beaa79c01e9250cd3a0b682960e7f39bdd57380a1e5cc86c806ed5bc1ac1e76690d76739305cea5c64c20f722b90c7a9ef9c93fee93b274d5e669edfe871b66b2e6e563ac24950d1dd74879b94d17b2b98188a0ac2258220a86671c922315c9440f91d95a1db3fa2652e07634a10b14e9da1dac088d286c2fa7760359b93f747ca7e7559aa35962ee17675ca6e4fbce9dcf06f12a390f53c743d5b38e44bee195b8d971c96445f989c282178942822f16adbaba579dd8b42992edb21d9d5e20b1f8f3d6048efc3fe0f434274949a204d365e8e0da5a9b253b076e68415e3bc7a68794871ae8fb368218ed23aa823c0ec6b32a869637adeeb31e6fee26e70a3ed0f2d4341de615fbff437fe7018e389dc149c3ae8932114fc0ffc33a4f1ebb9da8b20d78621c9093f94095f95a807274933b1cc81fe35a4b6e1b33b96581ebb1bcdafcb84c394e848293ad9e5614711a691af3e4bef2572319fad49cf2b4eebe577cec526dc980324e8d5193a9eaedf8c74b276cd5b08ecf30a7d436ed93d6e2b8aaff3f0ee98e40d16ce1ed6977d0f30f0d7bfef1eb399cece61ffb86b8e9b9fcc5555b61701f5a9193c9c5b3e44b2759a265c6881f7623ab2a5b7c5b98a81a9903c4f7a54fb432d2fcafc4a620ff18cea15894422ff1c7bbc3af585a5929f99bfecd0419b6d08070411b655812726bd14503e0a5b2ba9869c8efb75f7d453febf8fd2f56f1029ba9bf9f2e83bfd86eb3d51d8abdf5e5c0bad9768d3fe884c5c479ad5a47b02c2dd3ddc7e61ecf259b6b7ac409c30f86b44dc5703f9f8d591fa828775f7bd2b0e8f4e1e2bee0bcdac6041fce99aed6702c0a18d5f870faf3b23e9734c6cccc25adce25ec6eb2ef34e8857358bbc5b2618556e0514d6d4ec33619627205ccbcefc06990f6ebe1b01852a86d26e0d74f7c796f93bdd549f0063c075970a1dbaddaa9de12111c20153159441650027d416b94e0770b003c893d3a8f42c5b73bf754179908ad77cd19b6cba9acac68cb6d4890dd4b9cbee9a6470af5ba60679139be71ab4c6310de700f1ce3a7cb4774420714d30f46159ba21a919e86c22d910e9d268eda8b6ce774a76c6b1beaf90271379c4727ec013fdd8d9cf6cd0aa6fbfbb326a11dd25648fe20df30f5af5bf69b4eb1c48ff87cda5b5ced2bfe332876c6020e3adba30dccd7889064ebbec13ffdea9df97d36c29aff1dbfc51371be6a40576c5d6539a74f9fa088e011a0ac32aaa7ad17001ad2c65361d2c131cfc2f4ac70ea8134219d02d16ad8f1dc1ffab0924f3c9a0ed7aa902b5654cb4cbea1398e3633d9a297f9e3c08f27fe7fba2565c2c849d69329a9877b555b0e08ca51bea36fe4c6e481f0ac0e1941c3c82f8bf2a4d1f9b208a22dd6b283ab9a4a098dbadf9fa912b0482c74d2645a22673a01c1b364987b7be412b3585a0ae1a58ac84f9535daaf17f69472a8cc6f7643dd35c2cbd0a04d0c410b0081de86809c7871c612ef3f3fc10c0f0f9c632927cdc72e62b0a53b91ef55532234fc4fd942fe1969b496e71bfbd897982891a9aa8dfce2a4388b396395c3bb344eba748f877988c28c06bb3364424cb9c13cff8c5320d2daf556b288bd791339b4aa4430d4060c568dfeb6550ba09174ff77bbee32f23f0ca9184688e9f644601ad5821fec110a3415138e80cdb8d2405c9cc58c1ee5ad696c27e7c338aabca51afe4d53dcfe6d39086158705e8bab3d1ed3c107711ff9bf773f8b9c6cf1ac3eced315d83da6d311bdf220c9658c524e92afaefa8ae93c2712b00e5be0ae8a321cb1d398d85281b7b41e1b9e046364b64cc1ce2153c73fdb28d4b0151220e81290840ab72597735826f2303b5d1b3b75a4710300e5a11958bec607e861d0604ece41618aaefa4fddedc81d2144aa39cfa67ae27e170bd3c81ef9cd62496ac0ce01ad32e92c0eecbd501048516ebe7d3e74ffa15baf6cb7ad0b2ae8ebedd41f239ad11709ee60de8e92a57e596c82b71a81f0ad42204f06dbed48a5844b6429182ba8fcde64e634cf773bdd990942a594c5a689ada625aa26ce2535962f07767ff6c50c0711eb346cb0136fce988dead6bfe901684ed439467b3547e402319c13862163f2cd559817fb4ec94bff4213871f4f1729b50cd194135da98e2979f08d26d57e7207a5e2c1640db512c142f1e07a56840284b48cf9f041d2d407f36725b83030224aaacb8e4134272bb4fa5250eaa8ce750eb6992cab0dd1137e2dbbb2b39e80e0619b8ca5a6c078c184d829f0d185d7776bc6bf6a70b7eca90171f6223f927bd13f5af4d4105cc37794250096735bdaffb1176016ea36f8b1a094d6b9fed8244ce576975e2687b47cd1d1445509c58dad49142b40e2f922b5e6301dba512fe99b13ec34cd3f3e0aa6210df30f6d8e4d0582c9469972d64b0de9ca86173d05c1131f9e56a36419fc492eede3090be9c744bb0387c6b62d97c18cf6b5f3467d75a14c3942766c41d50b82d8a9453acb70081a92d0280210c817005efec976d494c954e91c60b3a62cf27a1e31fbac6615e310f050be2429f8c7020bd0bf730b99ee18c0af720aa4cbeacf78964676876f3e0f4305662411e0591d19ecfabdebada9e9e2dae05f3bf352ab6d073b344d41a64b1535176f24742054b4bc538f477ddf53d47c2a84c83da85c787f9bffea0d2499c5bac71f30b43d32fb5e41b0390a8f931ff0e26d69cf69edc4143903ebc716de6c85315cf88ad8e18362b9066206d5212b43d500b6fe05fac8022a2b3b81396f81c1efd34f0cd0166a2fa04119ec58b7d09d0d914c93667b8f336170b4c4bd02bbb0b92f53d251ba8cbb990b790cdf267d71eef9520e1d85059d02d03f9b28b263ba5b5590169f0a4fefffd397fc8e789a0d0e96a20999e7d33316c4c3a51a7eca76699328117f3e2334eba4161c0a695058dda2c0f7313b78c857621dd42efb7f33575420d0315137b4499124fd3094085545c9b3a3a0cdb222268c52af161fb862262e59b6f43ee46a1e85850970cd1e36faf93db9abb72ff421bf2f89085cec323e20d55fe4fad423126f91500ae29520721055f2c48e9e9b8002a9c920e165c179661a12686fc4221f9714b564abbf60ebf13fe6a447762102d80cfe4a23f56dd6fc5495810bc920dd482d5104bc6572647a699d47e61e90a321f7a7b1ad5ba3d38f66811c379f81add85ee2fd1ac459652fe3cb8a3335fe9a2a8bc38972fbdfe2f9dd70dc6948b6bd8a0171ee459815299f13619250f8d533211b53476d48981d0fa9282b3f420c0ad91f6e63136d0f0749a5d0d5eb5ce72a46b1042aed04d356be6d0d8ea6a27ce0d94bb87d6410beff45f9f0f1657f50ebbfa932db1c0c628a453744f27c88f8e023160a38d0007be5a1869e1bd974b8bba3da42d5f539a7aef60d8820d768bad1dfe3f91b4214f75e7612009b2440f8b437d28222aebc2f01672924f59f321782ad53b6978eb77d47548ca77b53464c0db2d94703eba8396d86411671d6749507608acdb5d66d475bd2e346f5e2f59cbd9a263727a38dbbabd19f88083c705b274962e60621476343a4b52c1addc0ddd29d5f62a6404bee4cefcbf2d7726046b747a6a18a3c73d38d5c6a53c896cbfc9aebbdd28679bd002b617003afe3d28224d3039ab9c54898a76d909b9bf4411d21cedb66c3a54f788b0409c63fb6da0659797da5d62927c5c36abc004419ea6dd0fe2bb529b72139ff21f66743359120914586386334f00743a6cee6313199c1678409304bfd3986120961498c203853fec73c7ccbd2e21cec1ed897d298259dbea04833b7aa898573dd99eeafd037fcd7d8651608a1834ec3bddc5dcf3545c850613ec8d4fba4fdd9c47188b428ac1f76282038005425ced7965489b1628a93cbfa0915774d0f4233caecff39d260e954f312053fb617783886a4c61e81765d66ef6a00ee22a7bd796a905d0b79a0d10ec713b733627d14a01f58a850806d2ec4ad0d4cd5a12ee9f878d564840cd4945e41333826407c7fa02fafab6a461ea2603806c65a82cf553f398da812df827fd6e9dac156b54725bc7f5ee31c07d4534a3e99c1d4cd27a9582d16220e2cdaa30409adb4447f4b526588275cbbb286dfd2209c2d5d3e6e29c918bd4b65daf3b63d323ae24cedf5de5177030822804fe160975b64ec944fb03e4c9c9608c3496e1d50a759e9a71f5c89e897eaf046ef6f97888a4efd659029b756c689a12941100967a57bacad387ac82db0c1bad46d89d6a65884f5546d5e33eaf8ef165a973a62b1634cfa8243989641037eb2d5fb082b86ebd38a93c91a34aedca48276dc7300807212b03b9c4a734f2bac1ce3e958887d79a54e367b75ffc82037ea4721e647d306f5d1f611fcfa980ec6cd8d4cc3853d4adbcf95b2e907365f450b8a6a70e5c0246c8a2a74e0751f4793d35a2a87abd799a54240cac0b471d6668d5beb6ca49a90fdc720c933d2d798da1968611d1a19edb4f0dacd0766ad258f5ad9f4388f20544cf3c8befe83170ad6c5d3e78a0faf88d11ec4cd73d29b6d97a273616a8991925c6857f0f0fbd4646ce54feabbc5ff0beb1d6b45e1f9d76db75b08a4c9edcdf99da02c3cf3b977e8dfc8eda5131999c4b7931bed2ee7c0e4a5727287c53a2bb2d91bd99aaed5debeac3d74db6ac7b2eedfe62356f51ec4562d2c88d0dcd15c45f501a0dd964ff69f9b5962167112e585ee2294db07f40865d9449e78cbac52a1d5bb5257371e98d64c73b64776f9dc8eda110cc31bcba46ad631db69cc3e8361b623f3442b2a156f73bcc03fa90f70da56c5b32607b0aa74b31000639be7df52496b98abb3d049a03d9a4a3a025503aec24ad3eab7bc4e761d8a123a8d001a0cf36363e7bcfa914b2caeefc55314f5d0b81799fa124b2d1b94093a2694bb820041edc89435a2d30bfe5ae1571d5f880f4560a97530be556b5e2407d2e0cb40b44abe64a4b66bb20b5e78a4db54e38bbe58cf99cfaaa8d4b4895f4e169089ae38d41edafdcb7a687186786aa4b94c9cc4382984c4ef2082b75501ac0d8c69eb016f7ae046af07e3aca35ae6abb298f42e26cebdbe15144303a6acc8bc6fa425d4e5f17234b068064986626e46c26b5d51817567dcb35a658774073d3ff0d8bb8bc21a689e446d9593542b03e5d61cd5e1a12dac1ae2fcf4cee2379348b7718d0184cb81b8769ada005abf3204ace317b10c143aa839f82aae9636f128f80dff1835033646f857f1a52ce94f68bbfbffb557efd08c2afe786b343f1fd69549a5615e802a6f1aef86fd58d8459459948cf16d23af76f1d5a5b04a14f9201851a6e92a5494762a37fd91d935cb6ba8d504d191f90845c6c01684965a7d574dee286476b8684041e91ecd69c1e5fffa8ceda4626be5d5faa74663adbce24c85a670d679f728ef15eb0608a95ff6379ef24a05c1a81aa8fa2d9466325c190c3c82948be6b3e562db412b5f6a345cd2039d75236026ee71f4b886b47dcca7093d6bef409a106a681000860fe903718f1dbb56b09d1de109cceda1f61385adfba7cc9695a92fc74fee43850ec93786bf7fa31e75f467dd67fc7157060c565db2225fa151fef9e191dfcb354184670baf18f7b6d792bbce5147de24767fd621824967fb9a51796450d3fd3496a3b963208c2a9412675e76396f98803ce50404178d04b27e4357b0b0c7df7a747558d47d2e71c4f3eaa2b90fb86e1222612c8d68c25684f3adf2a8bd869db35df6732dde108eda2398488b829e1f8cef291324aa9001855c88e49bd4ca22a94979074236cc38e60711e6d8a4674b742b485b19c80a2f4d6f41a0ce239fa472ac22f44ea7270138f3682747e6a7ac0c584001daf21fc6b6cbd35da60a0a7456c4abe34ed7f4d1da780aca2361253b7a913cf06e4719e87643647e476db362c6512524be486acfc0f1d2ef31bf90b6a0b9c3605176f80948b51c80a5b31a65a3ec512c58e440a49b7112be72eb8a72039690ae5dcb62de30e46f4a8e5011ec18de7470d7619371be0920585d7bb420cc06475b28561ae0f14981a1dea289c79fc58672c71041e848a6bc0e23bb12c85d9d48a2c58382eb4924adbd89351ef4b7eac607d30b864d9cc6b2a41c81ed131fa670cbc06ebe08ef56b924bde6c2b86a60f14f79d6810a492a0a8a1aeb16bb614e6240d67b18dcc9cb93b5d85d36c6336b559c66c4b33f811ce9b783bc107a94e85aaadfa994806af435e42e41386dbabf38990158c36d240ebce442f59dcbf7bb9f1efd513082c7bc528746ca9ceb4bb4f441e7d54d5e1b38f9180a89f13ad55f25d5ac41917bd62a35e474bf7b88799b23853e78adb289459a72b761142b583ab6c0d148f764a275fa1247f4f983f0508eacad7330120df42c7332c9fc7ff9bc6410033a55c2bb83a1d24c085632c6385e301be46a05901b0d14aae13d0d45b980427aaa239f8129d5fd55a154f0f1e42f275ccb94d11808035654fbe580660b737781a5333e45dc4afdca3956f22cdd242e54d9398d9de5127a51f639a6522455061157c5eef09c04f262b6192bf226d69ac3603393e7fb1156fe3a3e95c02754a1970dda4f76e23cf075f1dc9ba4157093a0980b1515dc9415136426025f885defb312a383e3dd7136011d807bdfcc148637bd9a6ad8cb6705aabd8e8fe292305453cbd2317b6463c2bf75c6539b2ed672dd32e4233d4341c82e167972b85db9cbd953f5c6060f7250bd51232aa236f0be115912d1b50eae57deb225b80220f1ce8540d726b6de2ff693bc120d5f9e75fcd7854634e24221d2ec12d333969f17822cb0286e861b906e4e69084aa0777f42274256495006dffd87a8533a189230bcf020d71f25904473f0ef662894cef3b6ec2ba39d13e2242524bfcb40ccc17eb6de929cd5a204cf5a97ee88d666bd3abdd725aa0e94aa9639b484807d220026ec8c672c61447fd7623d8d4d0c34c0c13376e37c14e44e0f58fb45df4f780f634791cccb5f791add865ffacd9f3216a3e80db279bc85c73fef90cdb7482aaf6bb388bf58c90ebe94971191c4c01a9830c88344f779883129e9d9b868ce4b384abd77986fea97177805b22cd6fd9ac2678543c9aeaeb498b911421bdc1aa9e12eccf81b29c8150fefe9cd1cd37944ba96641fdfc270c25fc45a43f247d4349e226547d42d50f3f9da677baefdffcc5a53658cbfafa1dd9cbc089eb81a64c123c16e15f1942546565509fa602bdec666ef7a04aa55bed65c099e7789530101f0f3276c4be20b6a3d762934a4ff9ef561316a757c92de32c60eab33f87d10d5dbd1efd8e40e7d4616a95f53d3c6471e656f2b41ed2bf2fb73bf8ee0ec6329a2750c15bf84e11011c882a61530a85a6340e170e326c9c1eafe0fdabb6ea783a0edd68a7892b70bd9ceb9e6e66408e114bd2f84789752f23fc3fbbc0c9b15f92173aa98270501b9fe0bb219dcb99e4183b83f20fca8ee63e325710554d5454ca216278aff6d36999e3cead67953da8f2eac349a7bc477d5a00b642fcae3b60465723d54b0397ed95ad2b3e4f9ee87e53b25d71e7cb6af22d83975ae71df997262ed502fe2362beb3d42c244c271473d2237365da600b8a6e5b6c0410737147d6121eee9f0384ce8aec1021d10f394882c3f82a9b16d6f4ce520da90f4841fedc34c6b59b4ef825f6b5f3ac9385686b18d6065f5006e57e87c06b28ac221ddfd6793c388961b493e68b66e70bde4eabeaf8d3075d7187e0b23ca1ea8a9b0566b784a34bd5156cae3073dcb76a725cd00e3ebe55558f7e688749dce73c00662e1293ece79f3c7d3975ede30b57002e40f4fc8c74d8d0d9dafdf331e145fa9290ba783a72bac55b8bbcea4c1f10432fe2f0d76c3d73d4b2d84316e814fbe4067d298238de89773a36cdf7004a5ff885f4897edb0a3fd9278ba2981f726407eeb48a5eaed0d40a835ca17e9c8b19ce07fcaec2790fa96c5a468ef012b70e80413d4676f7546a4a2cd1bb426f99ecd0bef215e2b24bd9fc31eb1c5483b37b06ae526557c1bf1e7bba5416d1ff45e77d4450ccc664076a123e42c446c077b2d68381ec7d5409e53df0435f2ea11e8b1a090221acac466510166f79b4820744d557311baa231db1db087916a17a59b49480f55ea807172d16bf3824b68c3d56a63db4963e73156853e5c7509df7f83c7e470ce05b08465511a54443a76f2882fe9f8774e15168705248d64081d0280246ce87309c0f384120972212a451afb7e52bbd3044df583dc469d2d1949771767a2b34e33fe3fba0fe22498cb15992b2a759a839688591ad93e77633eaf1f8f0f59cebb85b2afaab4c178ac776bcf74d9f8b878f8ff302e47bb46f179f9a115968a6f9816d6bb69a4e69b35ad1afaaec5b598724666301d15347a7d7af1369c4cead75b7e3e75ebb695d2761b5d08871dacad9ebae2b350976728a5858c650112b57def89751f3e31f2e79a3af32bf028b8be68bb23da951abd461352ecdb4885b6f2f8fa8517b0b9717c4c0e862c0435dd221ceaf2b7c9d833a9ef8d9031bb2305ef3d4fa8b2d566d7e560283618dc63aee97b911cad9cb91ab8d9a898e2a705f958e30ec964a9d4db0b06aca5fdbe01831839d401dbe384333bdea1081f566201af6b1494fa92e96c7fed3629c46a6c038b20e0db0db6c9883af64b2a6ee54b819df06b1187cdc8f348c7bfbc3c3c64a990bf3288269f84f93b423eb195e1e4c6ddea15d6a4873e937c9a76ea594bb22601f91d0925d758ec77bb8720ae64a4f680545905521c800433ef0cfc0c1adba3a6f6e4ca5aa0e1014b768914b4f0a186b4073f642a9e5d6ad8f681d007297ddbbe7349b96ea5e2885082e53ca32fa381e4774717a84b6b0f8f2176cdda8ffda146a2e0e4056897a359279a58b39d48727bc96ddc7b493262d547f1388f4141076b301ba36b87e728e44054cbb238cf3f656834da107b2e5c75f38559c60ce4ce0e169f18884d68cf3d20891a4a9896fab2bdf1b97a90e14cd016a39973301dcfa5fb2b95f8cd3d07c467ad1e3bc07e1707f22672be1e136b9a680cc7cb75c44201959f758a660df1a9069bb463084e60023a75f10a229409521e6a14f5f214c01fd8bb75cd42b49d85adebbbafd78eea368ebe17c49ce90352222c3a7c3369d11ccd37914502e4618597fc152ba22a4acd32240ba250cfc21d6a8a27b023e3a94aa0b9ce54eaf68128936a02a0a5539fe128e19cfe30b410262708d1560f50b5a333784172418bde80d32a17ed4798fa02cc4e0bdeec4a2d74313213bc2c438a6b28f69f0e2a50164b678aa464f3bc4c947d79fbff6bbd7a82a60aec674572a92c43a16ebda21f71abf4aa0c6693f6a714e37f42fb243859424eed88c790759d78534edea7cad856af480dbaaed9c9d1a0b3e037c48ae4c302d87e613e9ad8128ca83558ccfafc9e6d34e13e74895743f5b2f51f0af05e80344e30785aceec95954c8fd2a6bf1ffc5553ed37c08eb13f4f14237a3148bb01a6da578139fab54fd0d5751cf2ad5427c1147126cc017ddcaa3ec15815e6bfdbebb4031629b8e365e305e37b11a3b36c3d51e58c752d78efff94d2827cf0c529d68469b3cc397714ccf180b7edb970bc0552c97b3b8ceed714cc619a45b15ddc22f4c4e976c5f8876965052b1e0e06b9bd07b86757b48c29cbfa07f16cbcd7ba676d00deaefd1ba91d4c055f6fd129276500c51a2c674257ac3c6a1d44fb3b6160f79622e0f41bf565b57c6dc0491ee14ab5ad9122c4634e3f6201a5058e90bee85adbb907fe6e5e2f39c77ca7ea12ed65f5c652ca0757709bbf32ae357898669a7c259c517f67869a0e7af864f948269bc3eb45653ae8069258a76f9bc453875edd300e1f7c3da3ab56ee4fc90771b399f1345e78b1e84f35d36bde9176f6bef8109c7e862d3e36556af5c3c51cc598bbf0f968ea892c4602cfe4a3d59d2f12b993cd12c6b4b8ee1e587a61f17b99737a4af02a2884c6f55b310c49401e2f18c94f8b92d3a9fccfcdbc53dceab62b0931c7004853ad89302d85eee0149cf50493fca70d9e2283d1197e99ee806691a3dcbce6e7c240070d0ae700871db8f7284ffa682a218e5d35d8c87eadb8cec9f99ee0ccca1262f7bc02ff8e3b33c9dc3713f181a83e784333f8881e025f20aa1e89d9ebd048ee6234cebc7a5a03ba782868d2fe9c8eed45b4827a1724ecc29855b5a389c02ab12e29244dd9c81be89beb3a6c540ad0a754ed88e66987ada59f98000c587c0b89639e69a0cdfe7fdb831c02ae9a269bc5a571bc0ac3ac14b9273ffff36920059a6db9cbeca931b367e345dae8a17027ef726f3da46f74cf5022cf43e2e3e1befaa736c0703ea1c5a86ab022a6eb19777b5e03b34acad6fcf5147f1b7189beb59df2815c46bdddc4e4b31540c0dbd4ce29abad8559bb379df8ea7f29c5e28d36951e03138a02e5bb9554f54ac394e058b964a07d57113c36969df7776dde1ef1c0dffd1b4ac6618456239910e647904c502b1adf9ec1f11e3795d6293e9655cf94fe6900a7be8b036c783efdf27172a77c67d3bc5d3949a1253443d48bb14f357f233cfebd38bcb6c0d6f124e0e6b06b5e0fd385f88d9059990987480ce3c3ab196bdabde370140ef2f2f296f00a3d373d8466451abbbdbc277d6e89e23cea1d2302b55c1e756bf02d401fb7f20b167f6ee19b18a2847e4cf4eac98c2e5d203404decdb896e812a388b807d4bcca8616a3150d44e9c70e35244dccb511dcd9d2eedb04fd30c1e6eb4deb8cb68288a78f1446c000d583d98907d69fd20fb3214048ebebf9519a5ca7fdc916e7d1da8d6ddec5d57cfe43ecb65461b02a079fb8d2117eceb52794f3954cb7b3bbdd344a3e0bca91e5d2ffdc51fa211c59bd81c72e319fa54ca67909625c977b1d00a3471e1d6527dccc178d52e80d2d1c8d730c945e46fbdb4d566c5dce7cfdead97eece88d5f54f77a01038e6e4e9393ce55dfcdc271c8377c74106f863a42b6681bc0cabf8026437c44c5264d1249163bfcbbdf66794e5c5d50e256b41bd8e5f9a47861d2c7107d34f440af9ca720660c63e3f00fe06c757584cee57ddf8d3c9fcad21cd2dc403217f395e4cfe4d2d94d45c8a81100bf28fead46db2c88639639adcd45cd2268d8fa2fb0f22db184829c991d68c8b0540e90766f5cda9947e1e682ada1efb2bbcde241b7044e5eb2affd7eb6520fc2d1dd37cb322b75aeaa0e0247a80b7d602d582bdaed035a02e400c26c8d6e89e41e13761f498aa761cfd01be1655799b8a978182b187b1efd369c8e78f2560e7955d513bb531037b5c13aa769fda872e4e6c755096a71714995fa3f8adb12106def64060197af038d1decc2d8900d47901ea24fdb7c523fe9c05e7651d521d465af335d17ff900a26c073acf9e93946a07bc486c57ff48cf9be6014290779d9d130be2fd5394eb60c28eb106f82fc074c7d8dcc980211a3be97a65b46a6dacd92a19dee5b64db59c613b82e607530dabf70c79bae60ad154226fc740f0564e75675c88034baf778f844d9d224884edbcbda5cb5677b0ed655aa2e4de8bde9a4089686bb724a816a361367ba907c27eeb4d5a8de5b4224907e639f77573e5141955e0188e01eef9b4fa0e9edc2d71fb47c51b68e70e3628be145e3ec56c568b53fb9b5a948240ecb9c31f3879ecf0e97c21c02beea10647345094692802df93852b9872b3cf7c7e36a5db90b642dbb1094c5ca3fa5c5b36bacfe1b027aef0ce97908bc525c6ed1ef254002e1c64fbbbadb2bfd583fd2994f540f1fbaf9e31405e8ac0c03d9e6c60152aceb0caed13044861e4f6600516cde69c3f0df958b6c36c33489b4d4f47c9a7c6afcc5c2969a01f741c4aa0ae2038c34923167b53fdb6fcf7fe871cb61ad9e4ad682ef884144c58b398f58ebbd0864c4cf3c97a85f624a31ae7005f4944eb885090ffd36a110ff7f9baf8d808f93b729fadf0b17a3ffd0f1a691f6e3a6d1c0a47d86b14c46df1cf2fcfbfc96412d8129a42111be01fef8fc048a2024f564bfc41f698a582b1c503b8eaab4e6a5fe5394061b99efcb7f1fb8678cb06f4a59bc3c717bd18564f61b7bb52bf4a3656fd7e2c62ce0e8d7029cf1e3da98845a958f42549aa440c60080323afe04a7ecf10d269a439bece65c6b9479e69fb622adfd17b44121630626d27d8e123957d1b50bd5eead7f2e1d0edce19ce0bbcc4370f8f1b704b90152772dc020107516f16e6e8207542d6ebd13d23293e774b7e1ce44561a0a42ef699276519fc3dd6bd50bddb10ff7b16b8587b53e6385cf0e4c3d57c0e90def3689fb2d59a733642e97f3f053a5122a66b086691fee7732e7e14d6dfdab4133e2ab939214ca921a5b9e103b971f6d9e922b1a507968680e2bdd47c72d5ec72f70077d9e5c570c6ab5ed5088e040b7bac2ff42af993f672d91bbfe6ff739eb7a0f0845945814c92b4db30e72ce026199c79644ae4cf0832a351cbfec3cdfe7ddffbac9275140dc9815a20e3ab215929e19ac579afd1cb69112d04f22f116ceb12a035ad4ffea4df4aa6e00e724b355d169f41dc9e68cf60ec76093b9b34b7372da2926f3e6ed51e727ac8864ece1dbed16d08091e28f8f9dbee47eae014d29e5cf2201e82c83838b7a34b5f54bb4fa4d10bc7c1c93f7f8d94259a739778c309aa438cea46b18448cd926b0963daa8a61cf12731952479ff14bc8a2b0260fa5fdb847fed9b6b97c2d678419a78667a3101cb6f72fb325a8debbbaf9e9c224b472289bb4356e5c82a40cd33eaf6a30733a1f888416537253c8e2c22c4aeabd192be963c99322e14c6d1c67f0f17d18144945af3e0b2848ace6885c151e64b16d0e24638631c91a6ac22a74ae2ca16c63915949b89448226965d76f0d2c733c7cf47f74dce6744c488587bd3e6d53da6329a6b2203f7334a31adbbfed99b9e3d34f5bfb32304d71e32dc0004fe9b09eed42465351b5bf39857cab5f7c138f1a1e4f8f95c9b0a5f1601214801af3a448b1645abdf360dab67b92b972b8baa1cba1cb5f930acccd69c0503ebc573f4a7b0de8193960d8f91a5d308a9d0da9579e29810f56e4f69809ab7ed09273112559aaa172cdaaa36957d542748ef98596f99b504874e096f4a338ea9d39cc296cc21fca40c7a3ca3f440d390fdae5a4c5faa959d8a9c2c8b8ad7ca4b1b596b14f2629551fdce54702f82f68fd7dbf48b56ca3902b725fd8c6a6120c010787a7b73a1a886dee095b402c70f1b80df3a5f09f66f39e7af2295f784bb8c6a355607f17087468ea854b592d84566b70a050e56a98f1be5548a5bed25daef0deedc21fc5dcf701f201e8a16f3ad0d27a690debfd76eb68eacc35e2d3bdc5761f2f2e69bcd495df6d1b35f249340cb9165a713c309a1c5cc916c547ff905a847dbfffa156d78a0bc51709a92a17dc8450a6ff43fe0192b9291b2dc5dbcb071719c249a65e44443d51e5df7b23b51d03b0d190ce58a8a2ea38b23de36afd350010b7cdee9dd144a2de31b04c8847d3f982a712cb76c4f72fbc78a39269cbeb2d0a2a2b4e277a2e964335cdab8e616eab4102ad8f7b082c39844b57ae042003fe8c7af32d68e8f03bfb460763f5e958459c533e00c8d3075fbe774f1e0369159e74a863b92de4feb6045c5a251166b59148a850f41b5e5af131e2cbf1ab18a405795fa7aa33a53866aa0d279f026385edec04f81df05d873f62071199ad6d41c8869dcbcf1b1cab5189d23264ca0819d0978edcb9f3ba110056569d043bcf3788303d5764b54ea22a48b117b60c4e847ea0c6e8ac1313b1e9ac197717577d1ad13a43964db42002b9a352acee45955effc808eb7cd44b10d807328c6b08089472461287635fd2288a076ef640de5cb9407912f3c50dab708e2fbd5d8229cfd2342fd31808078261171a200d27e97b97f74bad9b6fd818daca7805bbe5cdcf4a707f8ab2e1051a2a16021c2e0debfd411fc7a393c4033a577aa3c1bc943619f98bac0fab0e9ecfadf792300387325774a52383770deba030907c2cff5d794870eea1ce77ae99850bc512d793cbe15ec4d98d1c48b253b735eef9464ecc614f4310feaaea9eb28abb7c3dc060b9c9b7cae955283b0b448cc874f7416999457eb8c9b5b78158030c0cf1b816913134238dc7631d4884ade3b369f0234a6020a474c632b9d355d02eec34d84156cfc74a656c2fdba9cf0e7bf598cbb40eb9f789a00cb2a2619a057208848abc9584aafdf55e498f63f4a57c9889de546728433ac0fe71a4729bcb024a82f690ae89371b3017f560e47fafeef49e5674b22b5b266b2d05eaaec990b158a6e9c239b257bb95337fefc6386e1d0a30acf5c2a417f92cacf4a480c2575368ace795b9127add4143805001266b151281bd98d52b4152f560b62942adcd9d2a5ae782ca16ef3b3b29278bd602f70af77750a36efb2e1a56831de0bb6c9183c87548af07a12ded902983ad68be8d87680c4f876ec29d6ead448bbb0ca841ef5b901f3f4f3f42d91dae3decba57c6660717b2ab53ffbd46bcaed2cf905c7966d3e308c22fb3994186de0d6c8f950131063ee53eef8b54e8f53cbf43770caccad2b4a24f813124cb0ac47aba731a995e2fc9878ba8eb7ee7d3bc833fe4bb3896ed43c9eea7dce11ceed9228bd591b596eb58c0c4bd52d2fb6c78949e2833cde8f601d9e23220374d0d9575a55612d7f5c68a7ea892d8427e245ec5f0756c1fe8667622d2105e77dd21aea4f5a8ea486bb1ee1eb99f0a28d38a03b8d271492f606e7a0de965395e07acd1c66fa04be328cb684119345339d20ddb79cc5ee19fda3f5d6ff4d3fcb05e3aeb241459949fcdc5787401bcde0bb5f8fb47c4328d2a3549965e0ca1cc3dec72d8026fde610b7606721374cc437d3961f73ee9ed1b7bbd921b66fb73f795fd2ac077f7e8fe70cda2cf5f9a881fe51a9c291c1b3417f91633e536ffbd11986a7a23be333016216c3960948a6a31cec5df68068ddce2aed168902afcab3c100b705edf2382ff9e9ecab6805873f6f28a04c302d4c61e2b42def4bc5b7d1d5ada0e9e9305bc38bb721055bf63389b3604620e566f5ada0c5859ab03a8fc5daca1232e79d11cae9f0be0cbc38cb3fb15eb8dcb54619db188ef463bdfc486b15883c9866f7afcd3eade867772c4ea92b61354bb59931c657ac471405343ca410a8c8851f158de914ad14b80db9f1a4b6aadae2c1c1f2cbc8edae52aac1928ddc3dfa57a57c825fe90fd4bfe5ac129a307d3fb095f796288b04d81e95f0610c26611a920c3060d77abdc6d2ee6179158331579dd6ebe1012784900d0fb4de853ba4cdc067ca9d20036c0f876d69c229da94d1f5d0bea4913f09f59ef1b4b4cdef5af96e3df262288b2d78ed8428ac522f13dd3d20b9afc3a8552a833594cdbb72bbf08e8c02a58bee39e348f1b8c811eb4f4bc907a733c89d547b7fb289310ed4c3e7103008b7242ae9b115ff4ee6ec35a909dbb4406a218b9c4c4e7d5f4fbd54c4f99c988d02db55f955fabbcb8c16afd26b9a3d612b0a7f54e5ccf60443809fed298ba6598e1032827da6a1350413d591c556b3bd1d0d837ddc603ae1e08e905ce98d76b6b3be349b451cdc7920c554f70ce46e805279ac1b38f9e9567c4e646279999880237494fac93e5f3e71e87f5520a234b8d94aaecb43ac2b3f4e913372ebc4def02a7e40deec429224a69d542793eed6c1cfc7a02f70c303e6788ba9c5e53ea68c23ae74db1d96606a0e430de1f094560d4fddf8ead61433529bd291252fbb5dc8be494820a0418a5ea5726ed11d8e829622ab1d7c9b21adc844913bb3fcbde0d274d2f0d168aab86f48f6e667f4abc38451c81dc969b20e8f5fb47a2845675185eb83c27f43bcc6897dbfb395e71696d36e8d14fc9f57e265bdf35c4439a4c45b5c03b394bdeba7569508435a8cb4e998f3d7e7b576fb482fcfd0d20826cee73095b7f001bc7a8a9876f9ee19e5d88dab72322f7292b7fde61fd039348043f8d0be695b40d6d009686e5af9c07ced16814700f670a273e24bd59fb21542b12a5cf0270ecad5099aa3dd011c5f12c1c2bf835a9b4f4ea13be28dde260817552f288e0186f386e2da8d6d310beda800700c3dbdb96957a75a02c279c32259b06e59b7920539e8df93d28c37ba26e3f0759de14bd6d2a74e27a73cf8cb445eb4b3b9609a343547b2b0fe1324daead0a13adcdb9a6b38e94a67bc051d43990f762b33421596db10b03a63b68ac99e2c53b15ec763948bf1b6e0acbe46c3c02ea8a7870e02e437354d0b86a0573eae1a51d2bf5d2c1ea87e2d5068a14a24272051eb03270fc40fdc4e0497d71e0fe3411a06500e20401f56b09104c86e63590bbbcba30e8c88ae54c408ef3801aeb39ea301111f1d9b00f842c9fbb6319c7379f7a7ae8cde113dce56d3e9855811213dfb8607a67742df786bf0c2729f3077e1ad75569e621a0aa886a39f91736402b10ac30c1a2083999a04d6736d30edfc597dd7d0344a22358c5815dc86f741bcdc6aacfd7588ae66955a4a2a9a453410fbc9d63b247849b0c986776c694f95667bd6c602703a4b1feb2a06e9f44c2fb40d653a631c442d17dece08b43146990eb37b5fdf3146d57818e7763bb5c50f3c7278344b57ab5a5fa859b90fda4b3bf9997007efcad0fecd1bed180f01bd0b5a9bd6d9395744c1ea222dfda77797f07c316711a8d954af9894b7497c713028818a140ee63826bd3086469bc2dec8925cd77f21dd9af28c44ea39a05b0427be9fbc0d2ad8c97142f883328f45752c782760ba6076e2e19433f30bc9b8c5920c8e3e828c985d4a7d901f213d585342656fa838a14ef4f04ebc668a0b05e12db7cab77c996c08e7866e9510aa90b1b9642824731a9480bd819d39528c1c04f9a2ea65c59e923e35b4b4f79fba031580c90288e33519c64ba76b2c573f29889846b99cc70717a54e6dc84aec33b22998e2e6217cf562fbfafb4f9fa336f4d0c5b5037c956632c0590a9913d17ca2a1eb63830af8f393d5f05162798ef6dddb4dd88ddf5beb8250790145cc4924101069356263866f25b8dc74d592c4eac0d827513e1b1ffcaafd40a73e8ea0b58bc8d6ae362e8259904f5e4fd18b888b3b5b02f938291dae8a7f09b87f59f3c5d747ea3b3d941079a664cd614cda8de367ee8e83ef52e3d7720e4066c97bc06ce3cd065c50985ade9324864ae1e99031765a262b72106de0911525e304762c2f69af84bb66196842893ff9f95401abf1ca6a090d49082c8c96b5d2645f74fa820b69d1fe756a4482442e9a106240f81d97d18a82f25279cc80afba7b717ebe3a7172454f36c7fa4a6305b3aedd446ddc43e24b94935bd46f411350d9b5040da0f1eb3016917ec2e848296f038cafa5cfad394a83db72865344876b9982f9ed4ed4bc918b7119b8213fcd088101b94555410e669ae41b24a5a5553174d42c3124eed78a0bc3e3a6180bafef9aae8170fb1b7ec67419e5e25b8679ff3b58fe95e5c5cbbbd33b35bbd332ee71891496697a7751eb494e377263aae00810736595aa45ea301d062b184764e38a58193d45c1a092b33ec5935a3b2e40b01142f2c1099f68ed2b43c2d825cb593384ffe04646f9e3421b0745f2cc1c04ad361dfbfbffb7fad37a0c6de8af29cd785bed01ff1f37f43460b865bdca866c8575c3fb9a546dc2e7cf3ec49414aad602a8dc102053e78af5aac2802b40f181eddcb53e19540c716a95b1bd7b82c05069bbff63ce1cac83054e22bfb68b415091ecf0290dea2acb8d7fa50c4c4beaa47daca5b7f7b66a4f22f38844d13fc1530ca29c089008671d3d0afb26552eab99064d2619e54bf1650b982b27078f3d0fac39a8d909444091e09780d568aaf94e277f55d1dee5621a1b70b1da2dd35d84cf82bd0553e8ab865ef8f3f7a07b4eacad36a610cad6975a826b4163bc80d1fff1d045ab0737d8d3ad9923be4babe94f114fa48d125f504d03f8784e8d5d147616201fde023ab52b93e9abe6e41791af12dbdb3a3adf0a06d5ef407569076c54381c540b3849367136d84d02487bf5286ca44e774bea7a4fd481f09c31a62709e10b0a7f501f2091a8e204ab34377dfd72589c6ba62102ed555c345ad9fa2492c5c99f446f175fe2866ec18539cb2c4be4fbe6c9a37a8444cf8155fad0f3982347f743f6fbe40035d5f5c92441c9be086bcb27a8e6b3dcd701884bd8d41d00c181ba0df69dcf83133bd5418dca7a495a664618adbbe4874724a804c14ab09e210edb04b1834eeb2a18ba7a1e3aaecb09ef2c6a6984698e4847cfbdbaaee8b323e61228bec735b052dc050683e80e082484defd249230ccc5d88e1e7779a86cd82ebae11ec866c483ed68a5f966eaf0a226ddf61e892a318401e06064e8c61bc040b1d0cadc667cdc59d93374c4c33773dd2ff887b4382aba46d25900c103677c52e9534428cfc86d5af3b624a71d90d69ad651e72e854ec18ddf31747a62863bc46ce4c6734342525e9fb84842f86cdd53d56d5c858bfe99df986e0ab0e121265d004a33754d12a39b20da93f55f37f1d9eca8a8ec33ea8a3d2fd17101dde6283b611b392ce75b0e817827071cd9644c392ebb70d3bfe32370fb4a3387cfdf393e4adc0d5f8ed1c8efec51ca313ad5c759568952004f3f1a6bee645307e779f412ddd4d5ea507c684eb14d4c53cefc1e781eb3f18e7acb74709adf9009f43645eea72ccdb95e1f18ce318a2554f7560e27af4693838edec03db853179c430a47c56b8883e2a7ff45c0d65b7ffdf27b52bc935db67ca1b6ed6f174c82bb817641aa84b0cd4825ec6ab464fff59aa0d28cfabc8d309226f77eec5f910d8c7c3d100fbf01717a33c86a47b9a2a024ef7495fb1abdc1db47c26e69d312178af6490382909c1aded8a2b498c53cc6a4ca1afc4cb9185575b8cb44e422f02bc79cf96913f14914d94e1298820069215dbdd054f0acbd2a96528dbe7dc927b728c710ca01c1ff060a3286ed105972bacf0e39bd831154578a3c86423a646d9ef76b5658aca40828a2290bf2cc950bf8285d3b0b3bdbc163d9fb91b695a73fd21efe6edfae6228c576f26f6599c348ea633bb3ddbf04d03da198aee67859edbebd415277372c2de2460dcc0e06bed03ef2dcaf244a8e2e5aa6cf95386dc8d0dbff9dc8c1dada52681415d4d431a9dd2ddc972a527804cbfb3d150bc93c4d98753339831a1961d855c8c2166b983a70b6f9fec5b1440c0ad49d1802d607b1eb4f11772a973be44c29f0127f4222ceffaa4185188b58ab963ab84292bed08200086e334c06d8b4c55ed59125e775e467ebdb9ec4c0ddd49d9efbc9cd2477cd7e880994110a1e597cee14ea460536a3742be44bc367742b04712cf2f7e328a1b7c12ee777c2602e34af2590c87070a14c44c75b8392633d866f9b4fe2000df188d4672d1f38814517004a259e963f0a9d5e5e80b3b2703e666595e7b2039d084aabad59d096a96f80a9c15750ea71eed38cbe03610d3eef396af73763c98b219b92797008eff27242bd9c631116e780698770391cbfbe5090cb8b68bbdbadb77b49123221db6daeb1d883daeef97013f1d540266ea7883939d6205fa39235fc0a3e9f64276990a8b4e4049e1aa45bba97a689a8a8f6297fb3c45641969c1f31f72bf459a95df91f27cc338af2bf1d10d534a3bb0b92d772accd30bb8b1cef99f5882486a9e9f8218145094d2007000150ffad7b0094790293ecbe9ffaca3d15e125f94c47470098ec40f01380923aa9848461ced61411c4e5814b1fdcf0cf8cc4530b7b5b4bc7a0553c0b54c89036206bc47d539e5bef806ed1179c2d9e86a576ad7978839daed420a76566034901ec1c11f509661293207c4a7125a8283872937be230f61f4721136dc209ead271541d27613276770beaaa2b18a1a973ee2515940c7bf116a7578484cfdb2e260bcf62ec443865dd9e724e1d1817683ff0c9fb68e2dd535483c70611887c04c2c97ca2ddfa8d0f33563006ce85b7b74a89f5b5d6abba6b7a70aa56d4be6c1cd5a3019327f56a32dbcc12463a7c9c4303fe7d98cbf5a1af7aff86bcb9d1707245063b1edadc1b7497162557e3c6d858bb3c13a9f4094d787e9e9840f02a97527ed0c4c447a6252867c62f43f075517528d578d2151beb8a65701c9ef1f9de570f26767b5fb1f7c88f932983928421ba625a6293e0c61a3372469917872a58a935a25cf3ca9e603ab4f5f67820f154d2f885363feacdfd42c1e9c6b5af36608d0dffa0113b992f6b838fc51d6f93fcc7e25b6acf3a4fe03cc0770caebba0005c3da010c3ed41b83c50d146542d8783b258b7954f608f5629f94e6738c9b928b13869483ffdf8acb1eb46eecd4b35614c18748dfd7171cb021e8bcdbcfbe12a93a6cb3e1f3423b81a6d8167edfa521d7ce926d2c746e03557c9d463135523404fd448910a13437d6c93b5ab56a0942223d987c691c0060f01d437b4e268aa0082b9b3f1ae34f73c388694346a6a2ca5c18bbe5f05beddb5051e747019f1b72e157020a72d31c1c2351d69a7525e1a328d39b18d074efd96c4a1bbb57dc369f198fbeb3cc157e8d72e0e682fe02ab474287f8423da9846f31623cb05b35a9a534217d9ff403750afe0a0294dce29de0d610d09e9e5d180ad708aaf4c8a16e7e0ad9c2391dd137abc4197daea0105ccc44ce44e76c0f6799993b66e8c596e21994285fda057abb97b0fc299877382e0ea6d8528c5078d0d273d560d96116f348474c6a26366b9706d1e38662504721aa12ebf9d69bbc26d5520ae729f0d78c90c33abf76c04e85f5da8fe7aa9ab64c2b5d3e1637103b07ae7fc3f52fcae9fdaacff17d20795b5f3dd26cee54f06262932f6b0ade112343e3b77f425562bc31f32b1d32f44715c28483ae63f4da4677fe8de7eabad797b451fa87b87596b971b2effaf147575557c53bd96f61c27ffed552816112ca4cf832ef9ebac1f8a77169b19b0baeedb114a8c7b904fdfecab086bc3935869f652b422751db091528bf0cbff3e4dbc53073fa4230e465a44b89499929f16e2e12cd62ceb9938d96606d646639efb3669e9ea64c27442aaf51ceeebe4b134f65edf631f3a0ebfa79dbd1e92db59695afba1c37223477ca56f2acf67e07e2857a6c2cd729e8fcf1a0adabfc8c51080c828092dc4c0f4644eb36e4e952141d5ea8555327be5ac77f06be23c61f3a225422a6a8a4b84a693150ee1a24b65c16563a215f68a3627047e516aa9e4f0847ab4fa25e38c32f9c42b3dfec8a1af004c866f2160fbe92609b532c784370c36997ace0de1e6ba265fb0a53830bc004ad0828a115ee4a544aceac4b698dcb0a1d856ef431d6316dded62b4d879dc769b591336806d63cb45169095467a8883a5ecf91b95f2f017757824fb0c99ef7792ff382099485182636e653727b6d1eed6171fec50671b93736874c20c90f0bfe2af68a276e74cc8f5fb57c7515b580d12a7eea3a52b25e52a52a6129a3f141510dabd7bdda6c1f45a637e74f2ccad69f013a30fa1d381045386d4cbad9dd0eb56401b0bb83274368764ed8f5ba575e1be5ae60740e1ac1f1f4c7f65cd3bf97e37d46c22260847e200e3c15ff3fd462750cbac38b477fca3e929945c0c63bafcdbaec26e514dd3b06ee65f3c97f3c825cb6ec47a5fb72a075fa0a8583a54027993b065896bda716999f00b9c72231d03908f78ac082ff93143d99672857bc6109758d6a4ba5c28b770008c1812f52b9024b38bd687d4da0a15f98b7b6f0f57ff517d62161ce4691861b367920532f943ca4bf13eef5db2782627dba9b633d176350b2fd5c8b21c55e7ac9007e93685f5b520bbadacbe39adcf86797ee4ca8ecdb808c055958948c2520d58d54632031d06de086ed739a22dca9c9bad96aca919c807c14aa792237a50142117574c0b13deda47442ff72325bbfd2d7cc75d4b03778cfcae2be9c040aebb7c860048130efb225d72f6be536628a2821050ae71b3f6effba441ee813f397ddb8e8073fa7fdd0a6d980e09fd71c342c82f37dbb822b97f4b240fecd6f34dc37d537c19abc1635bb86599ca830e1ab4bd8bf91c8d7fc45669710d3f9baf08bc93d7dbbbb0762bcfe09c949f58465e52e23b0ef1fd30390c85e19a45584ea20d2d5ea515e246e49b09abad7a66923316f95cbb74d7c573ac84f4b1dbacba8a44013eba6a6afa13b15645074cf29b2c4b17202224cbfda7928cc8052fb8ddb4ad4acbab585405b69a451a59af0430b5c6bef98e548e4c11f3f320b7b5fc33871c638e281af673cf6f0539a1e2bbbb3fca05a79322034206fc396a19ff82637dd79922ea9cd23b9ce9f874e61c1668e477eb225e624a669fd67f1e09ae1499b00674a2168a5bb8c240cd991060adff4930f9db584aae07d7eed6f2ba81e0f744b9393ebefb648546302e065e84f1492a6283d304ec3f9089ae7d694417f7f19ac9139cead38dde20ff3753cf6f97c749ff58f95fd421f3db00b117db675026455341cb4b11b7e0d860678cf0690e738d8fc7b4055091da52132baaf62df26f401a6bb9036b258c4e14fd43ce2c3314a4575ae3ee3ce7ed993b7090ddc3aebe0a1321e606371c203908a819a642b03bb269876fdf7318dda001ec57d180baad442afe27a1625f64087a2bf5124f2477a4a9deb039589372ed564de8bcba3bb7c05944b13b9c04717bc9f50da06e79814a0504846840d0ee2f7dbd9d79b9ef333fdbb0c0a71b5b171b5bc891ed4120c020565d7a2b83c24463ab6610f0e967633b3a67edbc3f65412902dbe5a0c2dece8c3e2a52ade474d965258b925bdccaf78cdbd3ae6231a3f96e5bb71f285a55be261aa07be3a884f90d3b6e3649bc145459d57098dfa20c84665f3e9227192efd84ee35274a30b815f0e9dd0780b5c84e5d59156242d6edd19ad51a583101b3905f949ee83feeeb691fb1e12847025b6fcd7ee8da58aa49994bb032113bbff62a75dc5a08319606f65a2d7aa90a2c5c564f26bf49f226bdf7aad00e74204ecb3bb6874124e49c1e31d65a3d597e2c6a731cd3bb76a3035f5f43344c76c7a60851c5ec5f6316700bec656523ebb76e3bec87a25530c37df218f3dda5965f6d5d325629ea751ab144d1fab39be6cd6f08f53e82080b90723fbdc3e3cb46c59f3150255d2ef2c35468d579f56790fc9194ffdff03ec4ab681334c6f43000b0d14d9c62f471303db72bbd398222dc8c15331072669c09f55f1102ae539814f83c6484a5c6b38671d51dde62f26e9e92d939745483c91c0681ea4ac247dc68640a8841d907a37c654146e4a0d575eb94d8aea65115a06da0787af5612bd872edc90b14aea77f12c72e4376eba1442839abeaed4fbd570f9a9f0b2496100d8a167c6d02760b258056cadab8f55e6c0efeeb25113afa930056c70d6db7e0ada6f2644eab02037f543988093354df1fc718315960c61a47d96893bd2b6888cde6c5cdb211b408de4a5396f0b70cea22248191764d49eb17d0f96e49ba87dd027b0cc64a736c737749094c5e6b044fcf5d869a94713742db5b4d10958f68f090e8c3a06ef336e0634c3abbfda500a5fcf84bd810a67e5be50627978b44e39eca81ecfe5024a217626f7ae4de512a7090214e7bdf7c296e137b0e42573c4ed2d86d14784d43a14b5776b08c24beada9193c0faa829a6ec6a3d4f4c07c78e8f43bcc7361d39c4cdb63bbe4fca574a61cf574a024df26f2477fd48fbe6cdcd984cdbdc54da62a6ef9992d36ef219253d85b264c9470b892faa603177e942d50c8dcc9533bfde89333d6f19ca177b0cadb969b2d532261b2abbf8fcafb73a80ed9ec1f2cd6ea0d9a4fa0e93875d7c2cb3a2a9a63395a04b25604319e7a2034bc5c9c9c1475e2c57e4f95cd0e2b8f72818fb7b0f3c31d761ebadb9c72d889e5da8251c943820f3aa4ce8e0ec1c4e473086ac89fb7466e8a13544ba3432300abc633b7c1dad4773dc1c6a5ec89e99467f1ae1ce0faa9b22d2ebaf28a78becb5fa857b0dc684350d563a56a66cbd4026b070f1fd5111baf2abdb6636fff5c4aeb70c933b388f5e61fde07fbb8759044cf28f13affd99a706052dc3f9fa582c717f75223df0c7f2adedf23b08034741ff905a0d257b78158865ed793ff35517fe9c535a4fc76dab53c57799cc90d9cb65c455ef9f25a410b699264d926588f6fa0ed0331735f1243c2f0eadd90ef587218d848eaba74d1152df01b3e32c974d2af79eadcc51d76ec6b333d252746b0ae6ca5687cec12221710010a559f480156ecf93c31b405d693e20e7d78e68c3ce81d2d9f6adbd45fb25e7da10b5cc21052549ec715bcf52e62a7e11d4b90b52ae9f12fadf2cfec5dd0f14572b2cbfe4d5a3ec552b747a783eac145b963a794ff9d4e5fadf8701a4eace5c9d68f70c8e837e1f2058efa94c7da0f67755540164a2794b642ec9f205e0b104f94957a8117ceda8dd684bab15c99a9531e54cbb6055fee46e204958757c3329ec40f0427cae0489fa1380d69fa7272e3489fbf92175cee942f0f28ad8e45b6ac38e6f8e7c70306f38287e49ad4bcffefda81d2d4ea352c94a27202dcdba237045f52c7455bb93a95346f46a189534184d4911da6d9cc3b8c3f590ac87e0a91b01ae532c0af4d0580cb05830f25047a944ba02c0dfb592acfa6e79b50075b30f19d4c5c8c8f5245a52fc2df4e8b0e0a84ba7d50346ad9b5c2ed3b77ed3a1a11faa94331a5120c023b30fb31f83b1cc7a0cf02dc3cbf85dafcada04209c1648d78bee2dc26d21297bf0ec220f2652c1d6a3b6c5a9de7c344d24e0eaa0b6680a23830fdbf2d61dc016fd6e366cb6dd49d63b682212d4a6fc914fddfbf9a825430bb221172fb9ca9915d029b4a8dd7f3793df74c1cb926cc8f907d08996004be048e426dce1f6204937ad9ad0edea82a289b5034b18f3c680db6bc022ea7bd8fb7472d0ed11c56e8c98d33cc9a3f7cba584c3c6d93693ca9363453ec4e2a87c548ce245c4bb65a8208b4bf5de2aa4504ab2dee43edf9d9ba9c4877ac99db9b02dba70b62574aeffb9b80f0cb06616fe2d739f73e40efeee8572bce74f372c598f045a91d4cfb946a4de4d0c8fad66c4280f16652fc5993627b79b26568b16c93d5f528e7c398eba626fec7da9cec6dbfba5f9e1468c4fe14e5d6ecda2da2e02d9e739e4282c636836cb1987726688aba203f9cf7dc49482d2547ee6538fceb3f73962c5e87c4ccef87ae7074ab3b6700b9e875173602fffc0443f7dbff4b0cd09343df7fad2d28e514cde8aeb78a7b21b4cc34a114355850d934e414738041b4cec323110551aa059695fe8fa0ddbb04ed79b677fcd90eb07c0fd819519d3d5a93674b5a20167d09a47e2513b3f2d50279d70b5319a4944d013c76a1328d0338e02638860daee4fca5b15a266ca5f9f0174a65b6c2dbf3e2250e5150ddf9c52533ccf75ab42f87ff4d41ff387ce62f7f8d07e52357d4912606163c00407179dcf4bf4dda75c5f59e6c8148c24e171e6c96c0ff59a6dd5d443a02d4771a24aaa2dd5dedd6c1ec52bbb9d6583f16c64053fa3fe37fbd7c9d616722401cc093bb279320fe01926a739f40f2fb522292fa24f37e1e099d213e8291491aeaa65481dac0c920544b2f2b1ae88641cf2349969081401e48cfbb262fb9a96a7947aabda1f969b86a9edc6915c51114c05a11a4f9a7af93a1f6e9f05a20e9d9915cdbdbe6418cbda7ca0aabfab8954e972b49a9e45c1b77c478ee309b52d50ca0506e1d94131380edec22af84e2902620ff1c70f330d07ab284abe260b297098d32c635f3abc4150d6118bb6f159edc05fd68a11fb036d8739b1b7c30dfe6716d249f323b9ebc026c7ef06b9817c4802ce7f2854533fc2c6189f10a2aad9381229e01d48dc13eecbd34490632e8726aa763d46935277ae4b69e9f55b9af4a49803b21aa624c814cf07d3a4c44991a9d1048820b459a27e8dbc5df9e373c7568f607f1d0c05dddd5de314d2e1c849156dc4aa9ff6ef3aaded226f668a496fe43ac6825554cc6193afa2bef3e9dcc7e582f0b1a65d9a48a0b014be977bca74f6cd139c20e339eb597b41594309f93f6db4dab8033ae46c656349036d0be9d12f53c5ec1ed4f64c1d5e6d612c0e448329a0274b9a52d4dc8005478ca0b775571b17468024de7d96944deb4ad86444b2d3072b35b198bd65c40095a384acb2b52224cd835a9ae4782c728d69cef52bfdb7e31579a30c3fcd312c6a3d9d9f5117a303371425d238eaa6f910344f05388fdc4fdd33f2f34db7eb6d5d4315cabe88d62044a4f419e1b79ffa7a0d363a79ed43f38616b6ddfde4b6891c25afc50b619688fd2022e9ba2dbcc85e158bcd267af849bf1ead8db7ac0921121ed176908ff1efc53b08cb54deec5c1aa19eb1512138685e452fe63e398081695121a39a160eb86d9a7678a3ef7c18a013d209c2218dd9f4bb2499baae92d83a362ba3110e5effa2fc7fb5ba0dd1253ed71ff6268122d34264e9fd00130521e4a76183a501522379df22d0c831d76a1f7fd8f1c2d949948979d532ba441632a9d40be3090282b32568232b71df86819f89b926e167e30db35e6b777479b4a632510037ccf82166fc0d81e9bf281f910c1fd510ad397c723d46efba68134eb7406ae6a81f3107393b3c80011056d62c58ce005764e70d10c266c4bd14d55cee3d0048d7a764a353d0f8317534d6ab933aab1f6a2a4e85d4a14bde2e14d9ba52b28d427fa073740be84c9d9efda0cc6d0822d0bd5974d8a5f2b2f42f95282620bc8c2de562ee8f013d86eb94ed932367b5b168d88ede2bff61af989c8e2bbff4f11bcc4e94d738407260d7438ede298b25bfbcf09e47992e84729f32a1e2f45375f98cc3e98aa247519362d7a8265febfb6e634de1405ae1c1b4bd1f1ef88a2efcf9cd9ebe944f51286783c7c15ced20f7e59e063684ce98d9244f88578cb32de33af99e49cd9cc1e80490e4f094955893415677937b75e52ec6717699083e8c9ef0fdda2c4993c68c04d378aa65444e17b94a8474fb496a05ba877a33a609f22ca466566625e7e9c2986c0709b414bf4c17bfa9edc02e89c190d358917fe247946eb3e4517253676747da8229741645724084597d352412bc772a964fcdd558475a52620153b819b9d7949ed5d853b7314177cdfaee4677aec8f7cf392849928096500bc7dc92b30637559f088c9da4add3fd7a4274de086fd628cc459a0e30b8b5afa1f636c6e7e16078afe6d67b54360f53674457c48f4df02edf2129f650f9342d82fdfbf41a3c0ca990b96ec33bc9d559d186eb65148c8c20101b1f8210a02cd3fea71a5846d97a2c9ce1c69b4c5c2cc2134ae18c079692d12fc9237267ae22335b17a4c7324628517a09c6eaf0dec466d590af35f39fd08ea5f4b7a51033bf9bcde535b38f362a713c83c52d405dfafd5679d9b02bff916a0041fc5a69a51e135d841511b8386a795fce2db3cc649161c23cfcf23009624b1d086c9ad779a8f30ac14101c99a0d8ea15c1f0f95184db9d7dda83246ac5bf2d39dba80157c07a39dc6b1f7a870acc23059e8264e423d18742deaceb29df256033ca9f1598b153c086472c362c1c952faa5c9406c374b939aee2a0cfda21a9d7ab61785446c53a8259c18e30daa0b7673e00c186613c0fb00298865a2e8c4063ce79ad403363238ada6e0bbdbf1c0ace309d5248e5f28776d9dec28af2b5f0cbd0f07bb3daf4ed326a0c027fbffaa0b3c31e8022760b9725c884ee9b5577fc8c718ce735f25cbfe9c4ff81271f8cc96193955c1fcdfc40ee78728b6a40bb981cf898f14b68c857601c1e53c99ce02929299dcf2a0cbf78333549794ac283049b262a273322c1344e08abcbc9ab652538e2885033c2349db337a88cd20b7bd81e9d0aa05bb1d5096dcac8202d9d064d11a61dcb50da0ba4beb5a5088958df3b4444de85d41d7d10bcb1e95a81b994ceed7744051093ba7366b40c975a3e774d527dc29d516cb44418c1166b1343182ce171db18c32eb3ba2cd3898e3cc8d7aace8a1b7b905988b43087fbf27b95276f7e50f159036ee4d124e699cb0cbc140e3b7d5595eb3ebb4fca057835c01a9a54327de0c04b8b87d96d9dc2c614d5c145fe8c74275cf49e9d34ef21e4e8c152c50af1e875b08597df2a5f919ac96af2726d4d05b56efcbca5665e759066d6be93eb48510eb00a0dbfe988bfd6c82c3a08039409257f20e95a66d02661942c5796596091e8eb40849dd8c7ace1edc2d1fe242f1d0236945f58d4f982c4e6516a2946af42b073cac3db4ac10284db5a8017baaa7f5cea1aed4b7037c875db3cd8560d4f350043c348132008c826e6f5f4d8c7c4120586443e1daa8a7277c93f42a48198be6fa355d4bafddc6ef16c1a19bef3ee110079119232211fc99d3f1e0087c29e96bf6c005f6097853f00259b1c2340479e8e9cac38261228f27449a2a59e8885ccc0c88a0943e0f434fc436078b1303030e718b3440433f180b5c19c794a89903363bd1c35d131fe3f559638df113f0bac25c660da8496ddaae2fbaee70c948a886999ee583be561ab5498157d975b88f66967ea74c35bbad3b1a3f9333ab1b3233ec87f87651a007200acfd45c239d4a47a259bd600594f63f485281d57723068b2ae01d13e8c6b1112a1e4060deab5ce6eb223306f42d06255a7ea93bb00b7f79cac8d04ab46cce26d3d7eb15cc48183eb44bade32aecd98d8d32673a3b219feb0c40fb7ce32dd515210a67279d0cfabb251ffdd6934b43d04cc488c819e9202d723b6b91cdc3b11ace09d74b9045d6bdd83e8779824741403d2e608091bd61868420c3b27cb2d033570c45dac9fdbf1653151a813a4f0bb04ff9342f1ad2bed13bd5c891d4f84ef4a353d11dae3b5985dbd6340952c3341a9469ef6ecd439fb7488868dd9b1caddab990571b4dcf07c6d28492ecaa2034154cde5c4c9b5046a75dc3281651a73b712d9ecbbb00002e475b0093a633b885e8a214f09c0e7efef1d01ef3c35c55c1a360837ba45cb941ea2ba1708374791c40e14e882791fd7ee62c992c03dc991d4e1d0ee1cc0fd2296068da6f35cc6edf9582661eef2bb90172a7341d5d81b0ec887e162d0ebcdeef8f94f7af746e4428f45dd1d662e77c0e11e3d17746df966de7a761e93044ffdf95efc24e28cac1eae57de441f3e41ed76fb164876dd1ab8f61f23aa95ff0b80707a5dfcc75b1aef32793e44a2af0de87c860fe89fc807396243ded00df24a181b5a7c527d4028f7d394d2921e006070528ba65c53b874a4fa7b67d4e1ad8012199bb3cf39c3200e9be2dba33a39c06cb228cb3fb9b7e8f1de5a6ec199934d7735338633528a99a99549013a381456816da9e563aa9ac3674ffce3ed70e8d68f4b83a01a783924fcb32bfd0172816ad2547af55320aa8d48e8fb51fae29a65256d44751b77704269c4ceb5838fb56a2be940d0bc5e321cc38222603c575b54fb6d6b9041b91c2e40c46fe673f0e037787ff98934bc8a78053d41c7bee0e5a11bb55f9cc82774341f8d73ceaee49969b1e07b629f797990f5368cb298e50376fe8f36ce4ac31925aa877feb1e0d4d052bf4cd8867c8f1a732a499e843603f89be019e14f6ef64f96d698bae5dc5dcec3ac3bc9236a5772848ab62fea0debb8eca924c10c0fb45550053a2c45c3877e121027eab68300637846063546a1295d217a40a275b1d44900fd68d5aa4c7044415b9ec9c4e4a7e026603ee817f1636aedd4767c1e063fbacf68fde7b529ee28585ea71f61bc4e09f04fb7b557058c6e0e4e3232eb943fffa11d3a451dca22935d8649cbc4dabdbb7b212abb08173fe7fd0a6accee2b6c70e3b4842f764f37f59be22064059cd56ea546a6745393fa5fc26339abd308940d2cb1ea282f8fc0d08da4987a8a50e3e3be094c75990e342c76d2826044a95bce5095e062f3352947f8e0c41890ac17bec5584252062ae21e61ed1aea795f4903289ffc5a3582e9cec168d80069233fe9757f81a9ac9c852fc1edde7f7a7ff2dac4d6412912dfd3c599372ce252372bb086ff700395dfaf8e87458898cc263de2773f1a4af2b3dc3dd5ca36286b5811a72f71e9e7ef622112131004c5a772ce5c60a71655d5ddea8c2e94c07d61509bbb64df583d81ae1d2452ac0ee2530dd6a7c0106c98b74e8c4240d56a8d1a5707f4039eaeec919f2f1536895b5dcb8b063e6bb6c97aa1033795a8870a3e4837f4b608e011e0200e41b55e2ce68d78c7fb4ac49b4a2d3c9a858d45b7ae9e032e3a78f659386ee17fbec3015e63d190dea5809fd81c88e19e4edea58806c408ce43de0710df491a5c97761ee23554a3e1955dbfc86e42a52ad8ae1221985438573efaf70df9a4079cf99fa46f14456254091a2db1403cc47f98fca0fed4e225408b698b2a82768ac21dca8f26a81cdc85254153213e1819cc81c81f2d5d4eaa2a22b9724156141b8f5f63eb7728b7bc076af58cd9af01e57029be64e10ace9601b646c30b70a39ff22c4685f9393fa6435d50dd4196eb7697949261cde98450d6cc2d29868f4915941c3b55e17e152937bba04c02da9cc85add60688c09b05de8ac4d3fa2273a61d01fffd3b15e6f18c55857824116ae3da574b246f41d1413beefb8db003207c544a297214bff123ae5689762c3340ef83265c83743b7ed1087456ec75176f781a01d5dc2c0c72c2e1ac94c1092771ef2fbc8e968ec81709f5352f1dd2a7e0396e536bdfb2565f63a9b91209f0fcd218f7746521b74ccf696b707fa4d532642bfed743729acba8c555e2709307364fda7eef06cc6d6425ea8f7dab755842d94220465d3acb53cc43980e013e5bdd3037833a954ec757b894f22e89203799b3934506fb3a7a4275885141a7172f9ed954a8e6642ab9a19d988e9a4506881850fcdc3a1f49c894d8b902fca7e20ba86f63baf6602e38e4f52c595fc835e11a95b23a8cf4be6ca6c739460015af233f0341c18742863ce767628f3f84f932ca40fa63005f10a6f28c5c55f4b9faa185a8ad4d947a1adbd55efe70b861971fb522399f2347771f8782447fb7a1041d150701c67f57962520a9e601eef5ac08fc7d138378baa3259d8b75a4e2bf07a02e35b92cf264362d6cb4ba59bef17856d47b3e867df92d87b1cde974cf5041a5957eb8ac69ee7bf65d08b4247c8980ef5b107b92e0b78f0947cc1ed4a6fcafd0f3b28b8ba3b74640c760e3b8be5dc0f010d3427a05fc29768dad55d16be41df1f62368e28e06e9e74a08cd3c210c69c4d9f0d242391adaa492d2f200c1e937288d6f4a226b3ac1c67f4915ba287067212ac26ca73f0cf3a6fee765fbfd6ad2580162e711ce8c23ee265385930cd60499d23cd2c7ae01191bf0fbd6554f5daa3d6aa6793f07d9c24c3f99d060b0112f4812a87a912fb8e2814865e93927145df998f0c53739a486931fb39576a5baa5cf7ba38c26b65c49289c822d47f3f449b66bd2ed48a7ff7c467038c9b2ffb95e8436371dd3428e122bc62c4b1c30b3222d67693897a7b955382be3c4352cc52fb144e6cbcb8a483c1a70ae2136fe0e08db05dd0f5fd551b19574bde646c462f4f2372f56b9ee54335d7bfcbdd59c053d32fcd28bc52b264259632d98e0098b1f951009a9acfe46d5d0a5046f72fe768d980af4691002cbf35cb930b3aa9d233868c53fd9c1d30d34b956a345610ea265fc4e299ed8de79d70bcfd816b4947a2dbe1996ef30c0cdf89130e9c3719886cf365d63d65f82dd43395ac595447905fb9780b45fa02f3672007e9bc09ecd0469574e3a309b870b29f7b118c8c10eaafe7c80532b5731a0975ffcb492aaa2894d10bd4262377ee29e1bfe1c4d11dd916d0455800c5a72ce9594a5e957bf2ffa1ff0c6afbf9073ba63fafb2436e9c0aaa6945468c71424986412f3927eed3254e5df8a9fc21e07665a38a71d1d69a34ed5802f135a309e43853967f5a0f3326dfbd44e74ff99ab86b36a9c97f1fed368f3415963a3303de821a7ad03a0aa0ca435e5eb5aa23abd3cad5c97ec2d7bc4a911a73b49aefdb8a70ef872191613fcd92c1b3aab0459e884fe075c8eb513b60aeebac04fe99ed2cab08fa5c8417ed8581fe2c6ee75255c02902427be04a509b1f807441a0abaacd407ba5dc7b7853b3ebaac553ea96517526fdd0951b74d40a98a75bee709f71a302dbb3588ce249856c46d4f1652d8e0dabfd9e65cfc3d3177b4226c0bc08ca0256e86017e613fff84e6f987b7a9eef68842075ca561e6ce8bac6241900e07ba55bb978dc1e1223b05a8c832b6c905e22da46eb358ffbea1ea63bb0f4a2f10b8f8da3f6f831ca555e5b3bbd66bb4d2fb01d851f04e051490257d7e5fa01e963e924724c537127376e920989386849da2bc0232b9e9238db91547c4e93f927a2defc39f136c0848760b429f9a98e27b3d60d0f533b39f6fe5f9c42d95f8d9e01dd43fe1a29c5fd3834afe2389a0b3e1c57c05e72ed9d82b45575007504a1aea35a3da450fed650a7e3b8810f6733ca2adbe7c51085977a34d55805baa8f697eaf1f480591f3309382fc6c8013241f583fbc1b1e4d54c222d82dc3f6c1cc2a4709ad67cc1dc2732ca76209f83f8d10972d8169bdbd7c8313f800b1d241e9d0aaf0c8e8d9c863fa5495e0d7649f8b7f44d9303cc87ade1d4fee7884618f0be5aaa0e9764df2f28a1d9806b5d38248517a08feedb95e40171c566b2bab947ea39a1de08a51d0f8eedc36b46b07dfe245880eec34ea2550c6f7c3e0010a6d38f94b64f0e7560407aca7bcc762fbb6e7792c9f593548f19992de163e1ccda1540ef46c8e2d6906b79f921a9285fd0fe1030e941b77e925042491cff1364ab846c7ab92c051862897e0d7130952c74a638827b5a03838107e2a65f22823f67673e60de0ec0d363e61f818549e376103ddc4d24b0344940553783bf0d3ceeed11626ea9c8bcbebcf38df8156cfb6ef50355f8e01e49a811c4bb54e568f40d0f6f7b686804fbbb085f5cd18e18b049be0546197beb625a1e80aed65be5755ded286f69e699da9dd64494982108f7f5798e36c837dc029c85e2dfa88b849e2ef9d1ef169aeace726f089e862a4aca5298b2aa453451baeb004efa009470559beb14a066e60940ed25cbc8ead7207d97d803d5d53fed7cee26dacd20b76f602c697764ce7478e2860a38088bbb76b23f0cab917aec5aeed06ddee37e476e08ffcd911e3b3b004bc0958c6a881f95ff4c031591829eff56359e1c50dc05285ddddc35c99011988117c543e33d7e0b34033e127ea5e8d7488b2ec68fce70b1f4e9d123de34f1e916e884321c9e1d60b31253a2b51e213b8ca8f35e68fcfbf135fd25def4c0816cfa061e3d056a8694df5c85c58963b03ccfa54938498ebb08327d4a0052c9af6d143ab5a7402b6e373db9e683569f6cc4892c8561e71c73aed8e80546606001a349999061dead35090caceede83ee0b66bb18cd3715b04372e3b4ac8822e6fbefa90dd294f20905bb8823371083339ac9d481f863287dce86c7118f68922e7a76e6bc04d8a92b6ddc6ce4a25b67cb6602f96deb65b1de7cb867a8de7f7f8fb80d27a4b6f782d1125305f328062d4478d97b23fd4ceee0fc4a45e63788202f8ee61ea98045f154dd6ab699fe6d8256c8fc6e7aec61394d7297be6aff270d60670175596c6f9e8446b028600831b26a39248074fb67dec8dd9bf2e904a5bf064d7d8c60551b3053945f5c24a7a79446495a9d43c393b08abff6485cd8612674dce6b82b3cd7b9c75ffac9daa679ffafbb2f22f2f51c5d31e44f3e6ac6db40216d2ee87984fd9835db733be0fe75898fd1d73fabda30abba0b99f75aae04455961b5f996cb828f2a6e0adefaeb2e7f8ed5a0d42af7708477c31d016d960e9b04faf5da5c0008b54cb16fc31602ec65aa76691f5e6d317e3b7c8ce1729bdb38b74be8921df6bd72b2358cea70df06598bedf89fb3f670c443c7953e1fca16a35a7f12d5beae99c53b518385ddc949649991c33913499c4f2315b250db9ea11f92e2878150aba2b0ceabf631c3b0ad06da8c577d3b71637f7a23b592ac1f3e76a9464a750f844dc2587ce06760378c989ba4cad0102d1d634f4b7555e16fa9684f2e0a86b45ee5048bfffd76025e91637d6f36828420df02094660705212f14aa7e80639b74638f0e6bd4fad37ca1a14b8d435710e2f79823f5877ee1429341572e1fad1280786d2cc0245f813a74bf5760059e741f8db87ce78d635b9e2bbaba9abbee0c5c0fc4d4ecc1abcc5fe73ab1bc4c7167fe3d05f23cbde893eebee5aabd1a806430b5d1b5b38fe8b7ca4698fe74b6e876e601078c689a7994b6e9f462ddd3c59e3ab1801eb2b76f8494b5daab9ad1b3f79a5a046c45ea710961adfac57adc7cc03849d6dc1030f5fcf208eda77b5436d223537d5e51a85cb8766b03405fbcc46b07124d48894a707e9ec879e5fd8408973deb061afbeef9a20b8ed40b35a33bd8b9e294f81e590a6ddeb3c54ae8f26a8f8788e89478f0b8aebe8e1a6de6db793b0a82b7d470508d49fa5a94224b3b5c13f21167413239ad7964dbb008862506e2356cd3e563fa045883995286be22372e8b9c435905c105dc941c46f87c26f4387cd24b452c14e1fe9c01d2a75c06347616fefa17c13246b7d9eb3189f4fc04a5c783de6bf102628ab8b07deb1ab3699c135a1e94b7b0c36933da0b9bd983fa41ccb801787b6eeae73efe45660907e779de2688b9ff363dfe8fe9283a37561e59872fa6a894b88adc825c16b0f7d543bec92fccee0ed7d10206142de7818ed9c1f8d6b7f69c785fe6bd07620edab13d2a34d6673095512765b09791cad253299f84381462b27a1ba8a99224d82d59a3d142bf3ad8730548619ba4b8ce5b292d5567b78f141ca91a0012e87344edd15e1be699a104323fca1304cd93d814fb05f14a116cfcc631bcac1209c2ce132485cdb142c923c8c53fab80454eac67992253191efec6693e707c4486559f6c2f4c6543e18746706a1a074392c4c5d1f085dd6458342b61a81f2402dbbd9430393495bc69a330f149a928981f7834bea794fd9c9ff63f27a27ec13b89123aac462ac836de00a641537c22ee9f78afca3816605c09819b8b62f9e8d5c934717ca6f478fb94e80fed7f3990c03bdff8b9dde51059cde09e91edb6ab0d3f07d81fc264158d0808419fc1dd4843858b921f25b36f01dd323ba8e657163ebb33ddef6046934c286699441057fa500c4b9ca32b5e6f535626523e25e62d604a646051393d6fb6522e25d4c355f02095b0becf87853f3747b8730090c2309a47e55db1d5f4e81bd6307d51f8beb85376d3ea81863cf9a783ec6b9cd65d964c9c8d88da2f6872f99e373542b20031877363681a92e18ea30d1028b05082e988feb44ef588ec27c09b8eec6c591444d0e1d92b8d74fc6a2ec1930accfaf040f435d6c180f7de40a0ee833aeb66c4f1cf9ff245a1ece6aa94c8fe6691614309af7468388e1bee2f6f9a127c909b3d68d0b529130e9034a3116c9ba81ea4efc178d8813a8dc1d8dc61fdbcece8184ea796f5ff3afca5b4cf35d42d601a484e9e42bbac5844ea8e9ea3a7728daf3d00e3dd21cb88378e1c994b2c0039ca094b8b552ce3a90f7177afb3e313825dcb58953e896c4af55c8d179eb18b424e6c4fda37ec212b2bf77c7f8d41566289d9d9034eaff69ee7e635d0d99d60c73cb98aeae72ba23135b351806a97fb446e679c998b03a912b88cb678e24fc849fc2fbd3f80b0b532b343069aa29073c0cf0666c3b94819b01bb7e74ea09af3a5bb75c0747fe775b9b3198b6d4baf3db09cf48fa9fbf38b600ecb9bd4eae9d3a837134f078b4009702ac7b20805bd6cb1ab1aba1254a48fe63a6d6abf522d5bc29e08257628565340c4dfb76c1e33953cbf29182f65fbb24428a0c18a70314982ab16657dae1ad5db92f916dc732fa9350a6eae706d1ed082b89aa4f6e6d568abdcaa3e0421c5c81a7081fe5b429fa88875c64f84baeb9d458dd9fb491eeb2d425147673e75daa8aeb452847bf755c89f85f7667aa42560220b10170e1f8649a7b46b27b9bad9b1d63512a27e707f76078fe1f220e3dcc24c7a1c0dc4493b6b03638ffa5eff2ea5cd91bb734dde81f4d79bd70fee27cf74d963cd73576bc685ed08303071a5603cfb71bb1e8392775dac396adece32c097120dfaa7a49b020c27aa2078ddd53dd65ce11d2aac3905c89049fc7edf16502f36409d82c69b4b043185677ef3f1bc5eb3e457c295d6b2e43f93e77c3c17a68a91a318e6ff2759d8f80ee346b75ddf42a829dbac9298d0725a5bb36a2143d7a794f9462a494779bc3741af9544e5d8a6317c0114f0c63c0add2e96539c1be0c25275354acee2e507a444cf53d7af23f10a5ff9b28e012b36c1ff2e248782aae31075a2dc4b0c44500e6486dccc70ffa931c15a7de3a9232a5fbc418c636665d205e331f229fb4372b924b7bd4e13c9e4cb067dac6cad6bd9dd609c97260cb191bdf934d507dff41935e5cc33873755295b910d9138bf95e0d6145207115002704a706bb837cd852bb2fd5cf9c196c4439df3f801d027cf75d67bdf9c8caf6de426796ce4bbc98157fcb6b3aa217785139741a0886f1b35d06bb91f971ad2c72a38fc6aaae6895feeb998c596ab61507438402be8ec3df550e269b2a221f4f36c44218e8ba4a97b47ec2972b701759eb4334cf41ef9fe92ad0d525376e25465dd8899e8af093bae53c9c885f48c46a72dd3c181f634360ed94a70513e85ec68e8e2f97e0dd1012e755204734ccbd44115d3d34acd36d43d4e6efb98ec47c64ecf87f9f05885514d6bfef45e33463cdd4e2e92647d97f685bb2a8f4ea474d5fcb90b0d1b2bef9d424826cba199182cef3e03f92751056e42d5d0de69eeaae4ce440f863cee537650c431df4ec0f391cc022ae6ca63e8aa2fc9b745caf23dbfcfd2172fe81d9475b78057b4b8de1bcc8ee5b2aa9ca36003f112611f6c6d9032c890f106365704ffc98f1d01285ec3fa5419fcd91032fddc0651e821c12e42c5a7f9ed6d4366476a25d0f7fd3f27a1b2911e63ca2a4adc58e0be2c7a8bdbdd4e03b75efe84b601871f8f0c5835acf2b7f77678393eb0348078d99ff255b3f2aacff219ba91a79aa6741305f5ca8132e10f23e6a2b8bda092708d3654f48cab95f3dfdddd9cbfbb6d93ac0ad8e51b5fd0fddd97bef7b178d9c1cd8038a89c67e79fe100d802b2e91ae49b6b9f0bae3535cf22b5b715a090fbe0594add3887468e850a75e97808b2af33395b52079538ae736bf2ff2bb73938084d53575e733b0513510730a1d3d0ac0316ce24b13cb69f985418d47e61c45f60250080872442c31d1e6b692637481ae62c2becb5160952f6ada9b7b2c8ac1dd6e5477af42f26613c66d702ba2556c11c850cda1716aa2a1a95a357860db2a91bcbba490ac3d194b5516a2057237e576e3b1aa4bf4ad7ece4787428bf2f0d8313b5375fb43d72a3452489f595e5035d8c1110dc444d479f7b661efbb928820b1620a81a35cca3abd92efbc1ff4229d773601b608fc0dc6131874e9284e4faee62f39d1fe908a4108abaa963fdeb60f9e63bdb53e3dc8b22cb977771f2adf5f00c10e0e8a1f3a4c193df5a30d81c782a42a7dcde1162511df7b249685b600d86cd3cae7906cb64bffb7a7091ffa51b97bdc06dd935b333c44a060b0dc952746c13c3c8278252124e248818a0e242280de5c465ded78938bc1ed2ecb8dda96e182b345327ed05f712ef8b2defbade60b541ac40b3e225ae4bd6163e9fde2023963ded5086939ce69960056bd523c6cc0331f6fe8577703632b4b9ec9db5fb0b2c4460e2f86a925f2055414aa7e89549ba5c5b4a18df5e05b92acc277c58ef0ac281f761a3971348646feb924a8beaa061e865d475c93e96e6af30e1823a59521afea650aaebb3d3fafa48a92cce557e8b671220dccd77c40dc2c880f07e7d65a1caa54a6f6576f6656cb1a33d951892f3d5a6c6c1c40eac8716b89304278762e63abbe16c0ff949a48f994cc80f72c049fcfd686516285096794b5178416dbb503c8907274fbf91ad928bc7ee03b25698516434e8f601a0602543095b0d3a9a130a7c686eb4e26971b5c7fe77042ccab180b0bc2d83d8ec5a64a95058dc9af8b77372b5e5ec094a120d0d7fda3d4a2c572d7b02f5ede9cbe03ff3c4fde75edd49d1f7979a8bebe3cc259abaeab4899cf6ce9b11450c6669a47a0d27f710dc3a45f8976e8f51d8b93853605cfc135dc66069c405c817577d3ea7aba6ff5be0697447d0ca1429bbea661e4fc758d0bd5f26dad14f36710e31c0807cf6e4934e44314e4bdd49536b653b5ea29298d378836a609dfdd2d7d6ca7f46175f016dcf9978da353a4f8ffe99f98743a768f683e0d94016c031bdc993de308116b4c598c30945afc31ec7150b3dce36917a48c2f45dd458eab4ecec7e1f7cf6cbf83f8d3e86dc093317b5c40942d837f435af2cf539d592a5682e7b0ce8162bf76876ebcc60d6007d44f497b1d6ba74cb791edc1372c60f663d0fc63a139d4c374bd21494abc2429e679161a8003f7d92859198a4cef52f9deefeeba4dd7e43bbf195c6c7403da89df430c8e87da0e7fa0d38a04d85b28bdf7b4d39c42b2e24a5c10ad22f362c23670c119772ef9e5065a60d6c2485953c389e317b5a9c721545763b1e946eab8d3476639f3252c857ce2e31e453bface344e239bcd72d3444645a18a9e03923cd59e6945b8865900e9141bff8b0ee61ee76d8ff9bd4c85df2652f994f8b550f9496114ff604e6fff24281908fa35e4d56e87ddd48dd4a88943d5653f42ad3db9a7d2fdd131bb3ea1965ba699c0867bb72fde0b3529519766a6ca1c7a2714ea5388333e7010d27c2980f9af6c87a2731f9e4f2adca91f3d17151efade0f549d49789b0df25d3df1b6e9116facf5c393beec6693dd87210ed0efc7599a9a14f927c92378905c8f355e9e410cfca8df3d9dfbaa109b643ac046d6e4723ae1c6abdeed266a3328bdb913d1576c92c8eabff901de8ecd2f0c21c0d9e2b0bf56503cdaff617e248753813f73799c80d66cdb1085053ef4bd0ebef79b3efb381f38969499f0a5bc951adbc027fc7816b9a47420a54fff225d0fb6b39400b59881fb97e9bcce8ea8619d0d15917723fbcd7cd708759c70700e4f7dd7a66854ab94e1d3b6b50144d82c57ab8872509360cedbc772985ef77e4153c856999b26117bf44efbfda0943fe6afff89d8566986c63111ebf768f62c35ee2d27ba61bd1e465e4d45d63db79c7a2c69a6a60f86c096a99bd80facf9c77b662b492bc303d6eb96eb29d87f67de1a99b6ef43ef2092b208008213bd4d5edb28857ba3c47d16c95506f4b4ae13b878991532eb565c41164c2a417708c7580f22c41b653e67fbd046cc85cd0f1a6a3f041031834b12021afa6ce9d2e27104d208704a45e9692f7e082843bddf3b447d7505f83d921088d84974275fc68271b6c823122f79ae40ecf11f1a68e3982d9930a891a6a8c31ce6382852b94f6bb9757e11d4cbb0f5f2da825f7d911ad658ccc6f88d2e9604d2668dc741d9830aa75cfcc7cc6407e55b75bcbacefe0227e974ad1387edfa65fd3190640eb1f846c41a41f671fe78d188dee9d6516b9f104447d3c65c7269c3683a9c88b77933ebc14e301034ffca88b6a56c30c092263d243df6e3da2e88b52a17a2c0dc1b5f132f6fd506d0e2f69437a699d86dc39b0697475610e3beb81fa8c4201aa70c9024989bbd37195beb079e25c9080109e37637e6d550088355196fbc8c5ea258bd7cd0bf7331b185719394678a3cd961f7986c80b9ef7f99721763cc8946c35e4d03aaaff56e47f15af493eb092e5a8b58c551ab70ca23ef23ec5f1da2471fea05fec1a812adb4fc8b4defaa9907d49acdfcc9adefedc8c36447d25544c1e3a80faf96c2050b0fadd2d612c4c73283d08b9addd3323ac0599b0a5ec960cade6d67077754d313be0d5fe3521081ed3219f559bbb121906693b9c716946d3dd9b096e8739111385d0b68aa02470e0d1e2f640ae02a99fcfc5182b4c01ac3b0da2ec87f5f8c83aade6d4a845fbbb932c996eac2e72d44f1e32784ad41837a8d002f2fadb1b77206f5376fd57d71fd4c76ecfbf1a715b5f1e0ad4d4ce94682c1a6e2fd8dc7b39b5ed168c158b0aab1a9bfa9f887f854bde7613b705dbcbcfd863ac38e4ab8293295affc14778e41d5826fe883d4501d3b2230e56899f929a075f259575543568598b697602877d5c57daaa74a97cbf6413a51e29ae1cbd52c34543fe03f923e0a568630b62433064820cc4a6c654b4dd603b57cff3ffcac9419961e04f9a0356d5547a591960ddf00d628ede2077dc6837cdc4b1fec53f8d82269c925222ad440311a5c033ac4baeb1fa6a77f21948ab132d701cdcf858448d0a555123fadaa46a002325ff6070a71bdbda2ad7aaaed86b5ce2dd0265cafc4aa4e194ee15a3f304ce00e28e83928b1f9244dd83385b99da619de23dca379bbf5446b5bb63811cdd66ab7de7ae3beb393a198006aa97ac6d6835457be01306b3d2ca72f829d482654ef596b48cb17af96e293e940e3ad66d8a48f0f4f99a067ea96cd0df9551322dc887ce5ee37da1cc1ce1f7e96aa49e597127abb729ff3d31e70b0d02e5e2cad60a57734a43bd7fc49e4370940eac58373af5b4037709e3745bb94e9a22937e8cac177eff58f01119687b4c9112702f3b82554cdf26accc6e9b3d7a9569cf54ba37b5fd24f2034dcd040676aa1f921aaf6287ee8ce15145b9aa193d16ac3b5e97deeb308c5e9c7ecc8780bf3bccde3e4a458a7bfd8b74ed75e62f3b3085c1665128e5e124522bfd3885687a22a0f592ea99b0dc1dda5006593deedeb9fc779dc89d2b8aa2b359c057035209df178bc5516a6bf5426cfe8a9ae4dafaeedd25ff2dd8369f335f6a651e4e5bab2036b3b045c370f7710246f9e9fda70b99665eafad1af4012db197d199a0029a98ed2a6310eb6d6769bedf55240e29131c2a00a3a528c33b2a95f891c284f0098f01d19c70941e14d7b96e035ea4cd3d38cd9c69f79fe70649044865a14e7e30352ca0a629e94a5ca559d645b9af38d33b8d4bfa90c5fae370543f8c108d75dda38e274f565ce701ae2e0e6507c74523419673db810ae35b37232f2975ec03fcafcd79e9f4e4ff6ea58f631ede1fe914c9be19409d09dc8872f0510e6f3235281a0e07dae193df9540409d45aba8a1e346152e163c406ed0f0a6f40af0f6530452c6b824aeb7c3332cface4258e5e5c36968a87e20d9a664cf4d1614afae54262311b7e68b1e37b28402288b9839bb91b0aedfafa0f4c9533f7e109bbe4f88d74b4a8ef6ba689b377db3e2f9f82725f47513f92406be8f4bb1c44b3d6a5fc17d5a82ef3a9e32bee59af0b8c58a0e25620727ac7ba8e5f608addca76a7377dced503a06657576cb46fd63fb2bf01f4f07877d0ce963a03c63f9d382fc77c38e4d9505457154c24676795a431e10d02c61960bd943e3997208886a0cd9b8a410b00dd26bb811e95a9bfa92f49091871c7bfe7d1515f789415e9a35f374f53c5be25dcc067bbfd0f4be41b8e01d34d33507664568ee7601a213dc6d3a1163196f28015117c654e390a961d530a8dc1f0bde79ad4fa330fcfb65ca11ddbdf034bd4d712abcd8955408a44982a3259ae937c1b04245a87f2fcbaee9d0d9d65779429f61fecf9d2a06703722307b0745a62c14803fe08b506b7fc71d3cfc77622d152bb4124b6ff05197743d8faae8a3a60682ebff6ea75f8119bf5113df0ec19a888c9c14fe6ba5c4a689743f2740d66e90bf49ba48923871f193fd190ee06d6e04f678227fe7a03e48797ace01de80bf0bddb4afa514f930d678e231dff43d46ba0e0cc13e26694fce9e0ee76912f7237bbccb25229520ba826c17e87f82e9c2d8708e375f34d759f24fc865abf1867fb460b881b4a77a012fa7c30c1cdfcf6ca15ee10a94a0bfae42ca05b39633c617955e22a8a5fbe6fcce76fc9a775f85d7c257b0a80d6b86cf8f659a506da7f3d50540abfae76fa9155d87aedd7f37da9cd73d5fbb2c8731a879b5512dbec625c2db5626d2ca0461bf9724c97a52d28d3bec245daad602825d3a08e859b25b6c5440e14f17fe71e6716e726d52221f952cb03108dd595e3d9c2c5197fa5a539f84f159e3c9f2450ca9de681e5c34c3224d94bfe459c78317ff45dda532de9cf43391ad931794bd90e4b1dc5c6fa713df66ea91794c974d5fe5fd2f11199a0e10973d84f5c0f2c161b5411179a54af6618fdd9bf851631a91b5518162ed32747c3a3438438b6a11594570318cf3eb2ea3a507b4838ec25c9fa3d60acc494a70ac872ea3dcdd804f1a1169f05f6e65bd99f6667497b062e4f078c9d1feb503d93fbd1d52546197dd82c55030339e51bd390e3ca429910a7ca516dc0f33a51e3e4b40485ae34a600aa88ab1402a1c2020b182ec95e318cb76ae8885f45c74e4756f4a40c78ad4cb64d6c1d76342568328463e4b7f4549b8d3888c8c52b49fe8dcabf290677312e086ea13c4bc5a421052391b35cf675e1b3ae4dd08db3158d41ac5f05011fb522bbf30f66829b196070bed9a904c283d623703cc2be1623164554f819f57721c8c2d7d861fdcac0cc69cd93a4ceb7226bb7b832be1b9bfcbe59ef27b2236c248f36f47765a53d74adb0a12924ed5fafbae06665d81027e281597e4aad768e9d1172667a424ae38048ed2bab95c32cdb0901a3c277b3d4036a02a73b993e00d6481ed3721889287af1639eb558592f47275fe994ae7014656f4017f6888dadcfc005c25e432649be3f662e634973daf9e63cf8b8fee489095b54f35200cba3a9cf2e9d650e1cdf7a0d7fc8759560dd1a0925c3e388bc0e30bbb19836c4e50bf7e1f3e795f3b8a4b7383743d8171c7982db2a240d1e06a9d7460831a12e3adad807d146f6af580973a8e2c7c1ca79b63e15662a54b928c694a3c676f21ed1ef03685756833e95ef75a7150499854ce1e3e641f18f7afe2e883f3fdeceb836da7ffc6b382e2e1ad4d49cc0a8b453543b6fa3ee7d3369ea83d6944a0c293dd15fca4c37bd575d7b16f4ab018b12fcedb04bac2cda3cc62f6ade88340dc6e1b0cb554d34e11913f8fead75461a186c4dd4e971cdaf8cce967b277de373d21cfadb117750ca26b50bf563db74037a4987c37e7875d439d5ba56a218227dedc9271652e7ed3da0b291501fbe39485c416fca69dc63ef74ca3be164c815c746d5468fca205fad536e377b08c2b2d6ceff9025be0cfb01af780b4a3c962ad1e13bd3759d9f43f844bd467ab8989cb78320d46cf905e90500d669c26aff13d6b8f61beec5f9fc108fd3b3a0cd6360fd5f3f7c74be427b5f440f41950b140c0a6d143f471ff187a585c02e267fd9725e477656fa1fe603a1cf4ba7982a012cf9b269e9df69e5ddf9f0648db641716143fb5204ad6272b65277cb171554af64e830de8dc1d24e416f12d786d5ec28f4ab9e2fe37f34680a2b0346998f22356e8f71d59bc234b6af8c905c1f1931450a170691d8dc0be76057f4f098d1d5afa5913aefcb88cb53e51f7f8111b561159e8fe08e21a97050d850fae237f4d6f37e52bc2a3bcd5bec7ed3faa7fe2195cacc6f6afe0f571682d8571cecf1eb134684a375e8fd07e0c75adcca3193d2e9e537608f71099f769acc99c247695e5b8073e709c245f59514e945fef529dd99869ab7b8579f900f34a4746ebb89e8aede472525412d9951b7eb2e599672cb5a16e6aa221a7bf4d6a8e4b7f614d38f3eb723c3e96186fcc642ffb3d7853734a3e46cca063ced30fe8c20f5f6ad68fd76d0afbbddd82edbe3653a1ca68ae2ff328b225e9769f23a4a86731bed27a4e8145540662984fde825c2024162d898563ec10ebd769932a95c36754e0857827a8000cea3e0223f83ad6b136762fcdafe9d7c16bf68e148f31ae430bf6af83fae97d170fe74d7d0a9b4e65de18d4c6540f8e50ac5306b3263f577bc1c3a74c6bec7bc4b5ea93c91369685861cb92c71703f200a4a8af980eec12840687d27b7e80cecc704a02f23b4bae6d5764a49d94231f3c796c343608f90c99ac6f0b9e067e0496cddea1bcad5912d018959d507d06a7a838ed6b18578ce63260d12b2c6dfe40a34ef008fdd2f486b4c741378fcf63144c5ca6a7f788988f40c4298825154a86814ec6911122cf26a3de91e642d3fdde8b9d775f734323bfd0bb65789296576782ce82dc0ad8acacaa0074e947bc2363da4038abdad7ed930e77b61f582e07e8e12dbf843bc50de0dab6415936d34799bf30a14222f168e8da663adf69fe77d04545a94a68f418f352e73b8aa1e04508fd84d290c396df7a1902990531fde38550df3911e180bfc40ab6b4651bfcb8364805945bae85631d1f4ad6615256ebd018a955a084a17f1ba47eed3018fb68116975cc27baa4f6cf488c114996de61e131d3c0ab44023c99acb64fa80cf0d82405008aacce66563848cdec755d6786d9b1d4696ef24e691517c8d29a8da2018d017db8bd6b46ae7aa62228698bfb1abb6dfa9e5012814c9885516c499dce9240cc9dd19dddf4d2ab85ac25bfc059da72c7fe5669f31f3bd8cc78a9b9c27ca88dbc3a2b4dca6f224079a9312b2e9dc99bdbe1e3b31c6c1f3c1efcc69a9bb0d5ec5db1ec4f2be0ab16d7dddf2f049bb06305ce83bd3963c83c56e7b35d187203d8c74df3454f9b4dc6c420dadefb566fa2c985e86625062fa6531e76b4099a8b356f05a944b97afde25d9e5c91ce75368b9044a44a642d8379715509cc660575dd8f739a68331555704f6e52e6eb57b620e96d43c1b9ccdd6a87eb7891802038997f7a549534f30986fd04d70370903dd45ba2a73284e1b7d81626b12f50b12c3360fc44318e500e00e3a453cb42924342e81d701ef3ccbb0ce9138a39dfb7a583d7fc270fa9fb60f18f570c294f5031c7408c17ffad515c1ba96adf5318faa9c3fe008f0e7cc81563a2ce17512f21302549105a48390895a934093d49f4b450fd1cb90d38c032dbbfe7832781ebb0a1c67c75e6d524b707f1e7cbf83c573790af8322fec46530312288b10c4ada2b920db49865c54a6cafa582fe18d6ac6f79d230b386c37c77afab3650fdcab9dc3204d4bb8994e3ac23ba4a4c0af7c175f20e30b489c200ed5ebfb13f0f3dc0b2b476cb845df2fd4bc43b27f608703f846419b99459c05ab0b3a18c9f641f159bb5b21f5359a768af3bce4b5f1d5fd81b773629b5726862769d71287fcb0077d759a9ac8edd8534111f7d8c1c578ff07a12d27869a6092e4e7a6913752326722feea3d8a7bb8121ddffed45b2cd801081cd0f638fbb5fa1077b1b6c617bfdb07fb2a83596652f43695aaf35d3dcd9d9caa77540ace82134e0dbc27ecd8a02d849c8cd5ab77c47b7dc71dbfda7e6ac475083de8f917c3bc2f8fdc519ece0dd740331ec405696b06c16e577f5efba23357de1e383a46522f573a9217918876a87affee1bf9da3820215d65ebbccf20260d7df7f5014dbcf0b7e7cfdb49769baecd0961febfd83bff5305eb1d50e7a370161790e814adb3c01c42ed3cfd82691210e29e421be988f838ee5d1bc6c1f149e5b957777ffb73e725a799257ec124490fa42b825af5e5f767eb231031dad133409521fd613df5d2d67ce0d5fc5aeba4a465e9b587092eaa47ea0e4bd6905e4be500dbe46ea97677dfd54a417df263f71b98a53ee996817477d606ab1f10439fa9202ba11212b878931f0017b596df8f860676875b85c6e52aca46c34ff36d528bb16d90f15dbda51856ee6f4c61dacf35d3e572d92bc39e121b8bb8be3a855935b8fd9989ab6547ab9769b9bfa7de5caed01ba6395da41f7fe58220f9205551c806767ce5afd187cfff98260fea940baacfe4c5293b4acf938430b85a815612e00c30b947d16acc39759357343698021b24d1a1f06443a639921b92380d2c43eb585954ff2451fe425e2a1f8e60d9f0212d23790d5ecacc21588d30dcab767ea2425c4b571b5d7017d3b24f41f834d4faa7190355f348afa8367b1b46fc0d605897431ce0799a671754d03af86d442c18f037a7e4818fb2f1c160a2643d8e2d2aef6e3c194c969e066dd92a3868a201daff0ebf5cf92b946844be789e4f22c9d55f4f992b727a93343113b027d33ce51cce2dc70ca5deb106c54fb7c8c5954ffdd4b390414dd834223560878969a94cfc3ecc18abe4da069916f5e1d1b30e13ec2e1170841fdaf8fd1186017151f744ce5577f1fcd69051659f28bf8d4ffe3578b9d86701ff24c1f9d695201972ee9bd7ac10a367aa8f76120626930f3b22323ba68fcffa22da7d105f4f1b0f051d88cfa73c25a8ebba87b0ad0412fd5ccd102b846313eec092f33d5c8bbd77878645d3b7f3b91a975d6c3cb944c6b149132de38a668e1ee4788d1c3a3a613180b4f8bc901a7801cd6672290b30da40f002e42bb6db758304fee5840958baf829467719df3bcfb43867abb3a9bc3f661f7751ab14c37ed4f567e48e73c4039d3ea67735a692e700862188e20cc542e64630fe2e9267a490e11edb57f85bef98e1c23f552f61074fbdf764a344324e75ae94c3dd35fe597c3b25a62dd434207b6f7dae0eb9e2bb906ed277dd6d7e286b845b8b6b4f4d26d995dc182c34c1099aaa8da60c742cb946ed75802ff68f6a3aa33659bbb75c9fe1ecc5155d0aa9e7471e77144d971e37a4c5efa4924f8bd51fe9aa4043c957d69ab6d5eb101bdfafcef84a7c85f0f5c1d44d62a2813669c1509fb1d354769a8f8164903d76988651beb6cac59aaa5fa295ce74c35286c8875c90976ca1ed089ac548f2929cd63859f79ef6b2143452bbcbc4da7e24f2419999714af755d42ecd6d9c5568af369881e33426f560aa896232a16241ffacecc9d944c043689a587418495911d6df9321842d01abd465d43660a09526527e2c55bfda3b21a229be2a0fdcaad7a8c3989acfb7578fe97206fd9580934d9286b8395e5996f60e04f793f5f9e1d506f1611d3056c5916fad7105d49c81da14ddfa16140a50430727235a125ca52e11ba80d1c03a6a5d0dfe9c2c88599896574d459b0b74b82c04a1abada87d804cb25c7122f9c230d7749807c8063269bd1088894144f0f74cf8acdcfc3e53ec7041b0a174ecff3981b78ad6990c913fb657c06af534ceead9e3dc5e14c0df612c25362d91714d425bd22363bac836f30bfdd3e593079362f4f46ae69de1ada9923bf26a928a9a49573b13253abb39264539c82fcd5ec17461ac0328c0c1b8a533f41cc4b1556e96a48a336f2a71facba1c25c1097c1a8d0a33dbe4775517d3ffbc1f14524819ee06743ffdbee1521129e0fe5ef41865c0aeb2832e5e4fc438bf64a2196a44e4cfe4dac382ba6aad23c1c3b9facf55af7b7c0f3f44393fa10add57979b7956602be2c01dcfb1890bd516bc3c12cbc17251a6c1711b2c0258e44a0453a94faf3b3171b5a8a7366b00311dc041aa992ca49891a81ed0c30a99b24a49b57fe3fcfc13a3bc5ff73c2c39b578a2bf5c5a44db200cb3ea7129af8ef3716832d9bbe342668cd58b9db553479a41cb815881441f170d9a338df975c2b17b24f9ad80f835c7ae19f2e2abb1be32411d06c470580f096d3cd47684c80c5aa365484013c354ae1ff1394dd1bed0f1c1ce6276b0a45398a04f66dfd7338b1ace3461bce4e8f7d2fcbf2f8323888e13f397e6d0a8a8c257656d22b1bdbe5ca657a40539783589b340fb5580dd64870cc2cda844ee1545770d82f929213d4ef1176f93fe6455ae24f89c471bc0b7110a24875a8ad10d0b50d734e918b5393f090e18b89489d3042eec010abaf1fd9c355bfa6b87e22be379a19841adebd73d8c33188724acb5c4cc961f502578c24778ac33b0cb70795b087755dce9ed20afe632ada4d06b6ee534a89ad4fb21c749065c147430dd225996bcf7c77c4b5b0f9825cbcd0203a2ce43b14d8710c6fbda9ce49411f569ba8a8b6d6ffe676f9af155c744662f8246078e905a2a82c2327af27a0802878d1b410ee243c674ef38c8e841a3de13ada0da73977d56e2b98cb2b6bbccaa1a019f39494cdf375cb439616358dae8148fb1f3fedd343dcda4531d7c82316e9dc2568ab85719be6e3dec829fd3c576d2d3a05fcbe605d8238f28d2f4b74a7cb29eb3e8f0c4ee923c214eb2eac3c0a2dca27340ddbaf610e85f2dbb3563fd862423ec0c6d30f50723714d7ac819eee9697cb9443a9a1bff0ccf9e88d5bdbd762375b90b612f8b9f52b76954968fa1733a8ca40dfc6eaa48c556e1da2bea631e048c7c82a131d7188ce17d10d9f284b665c1115975e738e9eb1599c7f21c501e685e36c646d4624f97be2945a71c42ddbf20597d136b4306a681b0c0dfedd0b6512e3a6e8238f230adb53da89e9fcb0e5c2e0d3a58f289cd03f1390c55b3a829e602d989ba412da72364d5782f4cae4a5a843b0bfdf9ad41cac07a1b47673d6464cbefd8b82c2400f5f563df321aed0edb3b7e05b4ec1606628bd7619afa01f5f20d62140f9737fd17179e521bc9a06f836ead6b3ebe429a1bce06aa3065bc948271d1ee8154e78908b5e09b0590d031b762d5c91ea32bd7c71cbb85efa8910ce71adef66275646b6b998bf1a68c7e65dc25c80dab0567c8a12c871e3ad81220d57a506b55b9b301765be36feefef32fbf7df7096383f42b98342ec1fa253cefa30a5a977bb6cb02df9f3537854d296d79e4529918cbfca7043e75920ce67558f5a3f6f9f6cbd631106a87f75c7386ce5f554c25972ec74fe1eb1e40b8e03f4e5c6bfaa362df7c2d4d40fac5eb0a02d4f3bde3fbea753048e8ed4d19e40879a6fc2649c0b5139d07e401f1010d625b833c5cd0753107686df4be12ccd024bc7126c8e026757df0f31180f7404576a9cd7819ddfde3b743db97c1b7dec64ec74177e892594862b06dc864c8cac9dfb6c09aeb782b55c357f53fa58e752350188a0db4dc2bd97d8eec069356f808fcf7096f58e48563d7acee6b142e55cbe48981596f0fc1c8ddeea87f5cb0b87b3cb89c0b352b62fa5fb0d728c8c624d70a1de4196475741870fcc1e899460f84ecb66388e561a5b9a084d98ceee4956ac9e397b25e0bc22656962d64eb9103dfb42856ed92c271d711e2c8b4a52ddbe76feebffaa216e91da21f4d5821b63d9dfc71315a6933bf13e3c72f7c457376536f7a15a419a789f05ff3cebeea7122a768bf60cba4623fb72f7a535faccd3ae52eb271d8faac3b73ee59df802220591891327c740833f04b4ccdf4c84206e07148a915973f04932760c394d10430d2f0778206e39a88f662299685001cd61df0b399626028ac4d0713ade958c0bfde5221b74222c9d7901db540a032b405c595d40a6076533e9e19efd8b6bba9f52d7318569e392b4fe7d3ae1cb65bb8fb7b184cc40a78e6a44f7215f2cb5f9dfd326d8f42147d8d9b6d9e9ef0224e1ca41a1e5a5399b4ff801a6b8b4ab3ef979756344aa2651f59914450000d98a8812f0389789105d88b80c008bb948a44a4be7f27ec45bbe685734f016835eaf53fb2bb25aa41587a815aa2d78f8d038b7eed76a1fc017616edffb71fea9762294b5d86d8d75e67dc8c658a273b65bc4de8cd5925b1c8215c0f1f952a9ee37d4a3632379140dbe5655308f1ec50900ef57c1689872782affaa7a07c40b352a42dd926ce2a0684fc0f0b5eb79ce4d71bd1bd6ab2901148fd31f9820c935f73ab356fc5d9eca9c4a827e8973c84877aad84060bd00c631c6c3d8b59f9d2404344a3697ad97831707a788f25dcf10c7138319be49072c56b04b6023b122d894bbb4e7f62ec13c18da88715907f07413a0905a7418b7e23c36ca99d2d71e63d7fa2090eabd85a71a2e403e59681a7d26a94aa216b0aaa91dd937f35e90fee4f9c2c8e9e8ffd523e0ed062d554b624d6e0560e6defae05efd90d914dbf788883f69e01bbf23ad345f9b076bcd665943be208171a016fa68744c9e8f71276f52e181f55745e618552c2f3e04112266b99998d2a01332ef7648016289fcebfbdb54542f2887024ab533925b6d460d9bb36c85dfa3cedd7e7601ba34039da7d3ecac091e55bec677ade60b79ae3900e45c43df4f9e4235c34a1a4623c188f0f28dfddd04c62ae897dea68415f54ddf6ae50223399466be486540c7c0750c84f2bb3269811f5b0f5ce0f1358cef244aaabd92308b44de372f07914ce91d8c13f68f2fad10863e11a1f989ddfae32e88f3dda34c22f812e357cf00300a177ca898559184af1fdf1a33cb6ef55da7b8805bf5afd3d95dc5401d1e5eddcc344cbd589de1018ab28695355dffdeca3e2d8c38030ef0cc4dfa37201c0fed4bbd2838909b4a3c0fc47365d50864d5f0d3bfd8ff046f64e73c817b6295215dc898c85d647577b23ba03926ec10940dd1e4f107cbccf562fcfb35cb22061c665459ab24822a8b53a7905bcb9596b1042173df497732ad57663379fc81bd281fd22cf7ef3a507e3995333f3f79f106b83933af08fea0cb759bd1f4c8423e5fb56d6e43ebdd128742fa6151aeb3cf700d4b10845dac23060d5d3634e0ee669941d461d003289351481158d7fd5707fc4fb32d0bfe39edb0684b0977df4a7c1ba8ccd52b334f1fe5e8a36a943570d5772611caef01d7eb54936a66288933a5e328ef25987f60a31fc638e82d7d1dd500b56cfac5cf470be2b844c1855f4f39ae79bb9778746996d6d96c71a02e945d26fc935ef1085907e03999448f9e4727b4627b5631e7b421759a73d4725146a284c328f62c46401712c28b68dcae364bee27ffe5e1728f8e10d48a5e78116f29f31a3461752dfa9e9eac335d05730476c891fad37db2a727f9248b47eddb97121fcb2c9a65a73cab848bec27903e4389f976141bb0eb1ef7bc47c5fc642109929c085f6446989f57ba6b18abab3623166017949f5e4d93ac50616d8c5719779113ab9d04a1812620b1e4ffabac0bfc0cdef69ae0fd7b119f08aa4686849361d3e782996fc12089b52f3fd216bf5a59e88e78d2ed7b45e937cff03bdeb9c3036580e5042562623b76930fe75c81d3dc7340f8111cf23315a8a28c5058f14f45f4d17cb4681a2afa4946084373941f5abd0d65c2358e5b309b15dc6828dc5daad39a1e40bbd550c79f7138f3c0f5a3d8649ddc52365b3469d913cf192d5cfd734d5cc0c23d1263db8fd6db06cf15c1db804ceb039ce2fc097d0dd150e9dd1db77c16733b38ae6fae6690a276b05e9a7439d9e3162e387ff1f484b2657e501e0825708b5c58c0b183c1026ccd3c558d4c3d9042d7550f12698db59429b79b9b81f177d3acb3d77a76d6a8c40f92330cc9c544d50b48b4effbe57115301e72a52e17f646eed468ef881953b8986c02d1478f19210eb63d1b2e3733a89891a6d393d75d45fa0085356f416e9b1a1557e99d73d9ba146609dff7aaacae3db1d0e41754a99aa8c416df748e6400b6b424557dcad9ae7189fe5d4ffd3fc3a4d07d4a2a545772b538e4c45f748255c0a16597b6c181074df5e7839923ae7b40540017447665913c8c62e7531c89ed416570cdd528cc7fc7a39c6bf0483f73b592c00b4dcfdbd4cb8168ded01b7dcb7e058590627558cd2eac8a268f1fb0c09d2e95182bbc5ac91d369a2cbfba093dea3900c4b1e082c8e0f7cc1562a9c5b9ffd5f1b27fb09ff24ea545107c0e5d4ddf655981a5ef73c11db6e16edde0f017efa019c4455915058eccb6088b932b2b27e76b4657756142381551f21fbaf58e96d7d62e0aaf2410a30e1288cfb599dd69ac58c712062b505722441ae34419aa5f7d73f7765dcfad31e03e604559f9a3a38cfa7f3db31125c43a39fe632304cb29ebdde0bc56f0855497d9d5abdd6f4314a179959b79d4bd7d32af358832f1be13afa7c5e9a698a1b4e982d4c351f3768d3a3c2c146bab39503c13b8686e26f3e6483e137ec44148b1e10d6234afd8bf8ad2eeab80c20d4fb7c4ea8229f89efd7ea68eb41e81d70096571215724fcf760f18aa596200f4d60672ddfc8ecbb483caf690640922f5d5d555569fef29754de3e240cf6f9ba5c5974610b1f19b0eeae75733a5a2426ee97ff0edc8a3199a973f83b1994aafcfb35be404dd7f1d68d9fff7574a984bdf422cbaa558abfca2fc224a05d3a93507c0848552da05223a20744ad0cd9fcec76a0a87412586f83a60adb6a5ce0f6fd61494aaf92a25227ccb7ff5f045102528fdd089930e6c964c12fa3d8c74a264b2cb89688d6b4dc2b4964ee72b7dbf3d788818fdde6566adbd932a4e7e99fdb4131a5e6e49c38590ea3086388efb02172bd940d51d4d5a852ff8b6e3c44e9cf6ca73137cfbc939e69cf0d108b149cdb3cfde31fd27e42419892714e549f78a00d5dc0859484509dd586550c2d750b279defa459ecc1e5ec9f68f7c9a3e71e9896c91dc12811896d0e88964f7074f654ba869759f125cd011c8d587cea40db6f2ad7004b606e51dc8f6c41c33d355d8d00f2662934534333b7b63d53572f86ab031cb008ad2852cdccce4666c1b47175bac4e6971159068c9523cd186f4c13694b36dd4cb2405dd1e837e149bdee0db13d374f0095dfed982eca211cd34ad967b080becdd31656f8e1fa854f412626f7809728477a1007998b57c13d0a943ea27099d64df3b21f9ffe96b44158793b33d96e0ae1ba3925cd9d2a3229db3f7a9e86b2532a1c7c0c77cd8ab650d57c3eb3b8c3ad848b8c159aa6c9469a7bd182b21577f16c62a9438e9ece906e9228dcab88caa07007cedffcd140d4981e1d53c11e6e086b7de7dc9aff9fe8dea759229438b32c734f3d0bc49922527919fea36d3851b5312c8deb875e1a61256142fb061d3a857b398b9e7cc287d1ba5cf14475fb8c0c0ee1d590a4c280b31d66f12d796bf33f5244e7c5166604aab21af43cf1900205f690af5d97829d2183f9aa0f4c3f604f6e40a8a6c00c95ced4f7a48aa98b16d8f7a9ec662a69b77752160bb8e8de2201ed90f25d5b2dbc904501ce046de0dfb7a22f0a172b9894872485c787272a33f97b90cf06e845c2ca232bec66692e0cb64da939991b760be54d28fff9cbbef782b0546bea3c3ff3e01043efe1f7f12b5af465076026b68974f6f1c36642f72d8161271fe7976a41acdbf9a3300cff9fc4059d0670f141172ded4978daae3100749a9433c356b0fa48e3fe159484137883a84663f7e9240f2118489401a096c0133cbc3af84b37bc451e4609a9326d01b1106186554fcef47c2c8b1b8b10cbaf118379ef34147247aef30d1ed064d0496c36c06a2929bb3a318ad0e7187028245982bfbc796735d4fafbd8ecea719a452cc450068ea9abfb5b6defcc7f370ed43f1e6ff20f2430dd02ce317565925ac3c8d366fde9f67ad3962748d5da7fb88afc995057607b9f3e0cd0e8d60334581d36fea8a5d9a4fd967e4e3c5ee57c834667c6b7743cc1f971de7e5e6970e68bf02d8e6d979fdab9e7b7e25df8f03d10f937ff5c0d70cc7eb281eb356ece0fdbf83325ee4f5a78eac0f1a97a778667259c75a540007c34f2078ec364199fafe371e302857ea0e60cb6c1213adc074e0e9b92aa252208f6b4c8e4e0f35a1a6ae0bf330b92bb2c423f2e5afff9e799789caf1c0ea2d24969da8c22e89b9766f0444c255e3d20f57b40c3c0b01ccc2c45a1170e55fbf282c3695ca9cd83beaa4f7d4f19bdcdcf2346a5432930ee82d4709ab9c1f89be6c6665cc104a2fe53f962e22199dc4b176f5191af05a6d6a5b06ced7d663c796f34be454264a57317053fe7a29c03b2187e3a287a6fce0dabd66aaecf8c89bd13fa64cb7810542493f0eb88f9d5e50db30979d3d54c5753787d4d885ee2fd453c3d39a9cad30da52c76d52024d55913d9bfab2338495b416fde91af3a463e2c8f76f88bbc0c19aa4b832938279bb5afbb842da08da0212bf39209cbba1b8f99f41adcc5975072318f98a388bec9e4118f7286a73c5fbd60353920a89ec7bbe4b3b3a1a76441b3f4e874fcfcc8b8023d4653aaceddcd100176c91a2d0b4622bd38eb74b83c0540d1f3ffa9aebbf7887b1a4c3021a36d130986f0758b68f7a1148e79f29161398ba5a4bdc2cebc7f2f9e6f0d0ab5e67f6c726a84e88e39d8130705ba92781b42b2f37434d085d3943bf173e7c865be5df82dd1054ad343c25237163c934e63b14908b190844e1a4226b37bff8c875e74f5789695673d8f0380a04691d2599069f68eba1bd13ad291750bcfce560e23b922a448bb1a2ec88bbd4c5f87fb9dd26b5d7e22691f10ddf419da9aecc6291308392615f7d13b92b14a04f5608f9e7e98d1ea94d71a016c143c6a07d4b94330953ed73c16b4679410426628888a91f11b940726b9aaface363bf8b3541bab118148ecddce6e8ec5225750b5cd039517bd6f9c1a9e155636a8bbb7be2f7aea25682c115d84963b81e75202cf001574f43d07256727417b912a8c431b72b5aa9d8ddd192fe03143cbb510f8ea2db5acdecfc177fa3d66bb6f7d32f73802fc60b977394ab948056ab6b1bdff2b61955f491e02848ce092682d117e997dbbe3a8d211877a9d13d34a64146dc59532f50391615c65e734fa56780bb160a514c2339ef3e99d6b0fd4ae347543bded9b903a4f6ff161df874f15b44c5177e947f3bdc979627830e2d4e7bf5e13500999e21263599a3fde88a305c4e301f5241741241aa83260b1d6d0ff4fc76c782c91e0e253ab65c3c05105671423b80afbec8b06d3788e62d6c9baf5d8d877e1589e335246fac6b85340cd4afd89327289bf6f18c3ae8ed1791e4a725ae9bb3b5f0bf433c92fb80164f255fd47658fc8ed64497e00186ebb47e6957cda245c6ef3123239f3c782a5e8a9d6a77d8040748ad7189d49785752e8a47a483b99d76484839a52f3e471944a4998142b8aabc729fc4ac7d30e527ef429ebae887e35885882454db71f151bc7517fb4b2959df41104630da97cbc1354d7e8d2ef43577ca0b0c2d9f46b5482427b434e11daf3db0b4bbbadbe7f641554d0efd5207a1e524975e353b29055083bbd58fdf6062ff39697a744832265f51606b948a4edb421811b76c064f3f059f9b4c6099e3b226fb8571db08f40af14bfd78f368aecb7cc1584ad96f76c18930aa3a774c663c363b21d0ee9585e54be785c63e774b3b24c0745e671b99cfdfb39345f7f5529b1afcadaa77d5b983ea7abf57d16f00bd2b846e6134a1a154c609a5ee225b5a65f42b5f3c315c9ce2eb3950935aa62efaa3817aeb6dbbf4ad64c0af93fa71eea4e49a3c2e9c44de4d39bdcdf48ca9825754f3191cce3ba159910655cef6726319230d53d37b96690a2213c17a552bb0927a1c4546018ebfaf8e826f502cf2ffe2806e394cff95a534beef9f661b0e04d7aacbb2b990aa525964f59bfb53051c8917d7bd1a86db4be1eb08b8177c2d077d307100ea54b514d40903810204ae554e3f32da6c47a97a6c718056e71cf1438f1dd35ccaa7730545ec8c3857cb1b5dc3cbe899cbd0bad158e541ed685e4bedd34eb2011887eb64737907ccea9bd3c4ee97a67cc30295c8c8da03520a056dd25573e2ebd886edd7a5a97fb3d97ea96c17ca64dbda5a866a122cf00a72ee873457b65eda9d1239b01381bee0b8119ed9e1ee4e022abc06117a1bddbbcc5ac4981d75ae9ad358044fedf7016a1b5c42e752c08b0274a643cc68a9c523691b7517c29da4f2f894093e8306a2c0b326183fbafc2a6ed62b1b58e61d9de3412a1648a6adb9a4dcb8d95a88df461258ae3dc262a1542e9c8bf67d8f6d1921d7ad932c7aeda3704f2544195f1424610f1fa2e2830742b2643d7f3172e6bf6be176ae871f9b692c40f5927d5bb59ca0274d2aeb43122fc4a480340dc5ec3eaa7387d3a7f0524d96b75fde799039c1520c32099e44df26ab0ea7cc6a4875b85396a7bf47cbfb0a3592c0ef977e48aa6f432ad6f05c438b3cc4f708afdc65a0f276dd589933d0197d2330444c708ae37c60162bf22f682ebcc7dd30a25c2c59701d2535fb5d72c1709e543b82f71dfcb0b3c705ea0080ced49430341232146be196c7b705a2c8e9f4a7992fb7b461659dfeae4719512b87add15b179e30bde3760024ebabb4b23be550e5f5799517c95c795273749eed9ac7c125e8d8feebd3da222b1dedf2a42b182d9542ade484f3e0b29cf2d08149e779c24d0e4b9bcc670a19c6b472d29632f4a59ae0a6d55754b1e02aa997fc8ea93cbae59a693a1bf8e56a4dc1fa5e0663aa1e731bf92ba715c6e24338c30eda61236d9aac667ce1649119e1bef9cd75cb1ff4ced3cab9027b835f6987e6ccabf9fc569693dd3897e4c8152b17478e18e72569e69d0c063af2e7b46fe857ce6afa988ec19e34974275ac219eb519e45de9154631c5202fa0c1123f62eb6ba390abefd564b2cd1a070efa70e8166b704187806aacc49e1595fbc6cb3f072bafb60fecff62ff6c8bc185990f7abf04b67243084a0ba59c01e51db74c99a8a52d0517f1e34d62b4776c9b2dca73f311500b58d8a0bbf0953a76914e02634ff4674e7834874c11ec235cafb8c61ff47fa82cfa1e3b69f65999c1d8b3675dabb4e8c3ca08f7ea6400fd0dc1d694790da327a8369f4f895ee94db713910f93afdc3c6aa96bb6215c6134259b96e27fe2317e6bae2980f8c60c1c69e4e28b1b9bc97af1582de026e7618dae34281a803910cf5d581a5f5f7c997c58678bedc17cecc31122fee703530154b8c84781fa445c4c41257b87f32af9bd175ec0dadaf1d54628184dfe7d7f80fa0dce6c95c70fb9b80beacc876e2a8eb5472f443605081e66620cda67c358d3ab2241303074057b64c56ea124a9f04f3615e834ae11963aeb7dc34b717d68b65323865e1d1e333da3d5dbc040afbbde80400aa71900546d7ffcae433e708c6488f28f64cd377190e73b20056c4f7f118e49446f3e73f44d1aef9505c44957cf90f1d42e7a9158e67792b2f1d1e728030f60e0c70fda388a497f0948df9489070df4ed5eb0a6538cef27d52d73c65d2a1b5fad737d670bdeb12a5c7fe7d09ce88e5c83aacc396df06766bbcf991ec3a61d5c594cf8aff501966381d3832037eb9c7e2c979b73f65e9c9c42770e65b24fbefd04eb6aaef11c1cec20b0c2833076b6cde88895c4e614fd038ca566dd1ce364c4ec6f9b82340f2d0e28085946bb4958c85dd157e69ee31f3d79d8eb127fbf38c4b9389596c2ccc3a601c626e62f1aa8e0fe3a378ab4966e3feef0a45febde740182be1c875b3a0f0febfb6c6b68cf16dd7f71926a05313e1d47c957b6ff15fc943802443ccef934dc1ae67865060a0d11929dedaaeab860e0e0cf86dfe21d7cb4e8162f6803212e12af56ac8f0534fda96d47d49bf26da94eb1361b3cda9723e4b71a7fc192551fdb0acef5099b32dc74b4692cb5656c8711e35d1a9e4d105bfe58174876f02d5635173b4f06a04935cfd57c6d56f62c73be0404025b50f96d1136531d3ded1c6128c814d62116c8cf574f0f189718e67cd711d30090f69e832b8fdb663976da57d51912e7d2a238b6c23193d8f60ffb9580b384ee6ce73a52c54417e9a6143690e867200253e66426bcc362958b19980921a1056296565f80329374d665e47987e3d8ed3a6c8f457cd6e9368a804bc7e8f834ca2133826e545813c6c84ef8bf8168ace238b46a74dbf2982412c1b14ad0dbf77c8470b1a60fbab63465f50b283f4388a188634772f62c0e767fe93e04682dc65053c3a8f5c956b896fc7d625d3cd1fc4bfdc7c1e5ec01526387c51b87b82724d288bfa628e3fe5f9bad2b3f8d4ca390d2140878b5dd7a0aaa271b6556f7df195b5076d7a9ba49a8ede3b5af37b2abbf885e2fc23d477152115396f37ca4b90fa36fb7c45d46e86cd36276f01f967341764ce3511b5a2747ce712b843efd0e65068c3625769ca2da537d94fbe54d8e76383268004b8f893654a92c2f25edd058a0ceea5b5a8fa02089b6b13a6e6f3657b8c0ee5054b4b022a4f02bd014906577e338044c0799b60100bc0c468fd40b4673b09aa3797e98e9f2c03aef0e94ce9f66427bee424ba684dd5022a74827924cf84adc2813f04909315db6547d91397445cb2f5ea05bad9e7cf78112d554968440426197307c3587f5fcb948cee00e8848cb9dee1b12593d69de1219ba2457c65fe72127af66657810685b46c49ce320f96d2aa85ff304a7ed104d9ea3bc1385b902dabc8d8ad1feb2e58cb1b2abe1511923dc15beb3adb2f417dcd736d116f65a21ed4bda2dfc1cf78e089ae9e8fc58fce39a52559d755318c760180e8acb08094ab5b895c8cccef054f16fc7d445522f78fa2cff425b8f04156a9b0de703be1362ef430fefc492832fb6ed87f64f5fec98a8a124c756c40267ab270129a973363fc20ebff5feb45cb4586ecee84194054d1fb87fad2a992da6b8d51f097787f6dc1f6a3d064df99fc222ea6800e1947cc6d2862f85f6d8e65cd945f641cf28387bd45f5c50288685d515e48621557b10f8767fe254a4f6fbbca067148a1646efb24ed6909350fae25fb504dd6a771777c4051ab0fe85bb04bb0596654dd729d1eb51df55593373c69134e58a58e96d6cf0333198db08dec25fb6ec011a9056bb97fb7e1a860596316a07c6bc68b7ac621062ac84808a4a58c204e1ae93d5fe479a019cd9d7166cf40e62de5af6392ea867fb40e7f1d8fdc2c420c1d1249f4d55f719e3bc2c457d94fb7c4595058cae664ff9bd3e32b9518f92f704620f6384d13db1bc53fb58c4ce33d0dd5ccd0634615c3a61181406e29e41c81917802069a4963d9494043845f4be4e2d905e2095199f5969c281d614043d49f1a0377989f432e6f9dad4117258282400da6ae4dd9754ba0398998e081ce20b8a4a4a9df925f996725c6d9b000e14322d8d0e5e9134b887cac1510356ac63924be8cca3576a9ab047ea8ba7be5c312564af71d493131407c1eeb71fa7e22dc7d0ee259629128a2257f653140708fd268fe13f9c559c299c994111f63e6aa53f51a688a292385544231a2faec9dd6dacd97ce8b2139ee80010b2c3a6d59761322bbaeeed79b97ad86038c658c0ce9ac45435dcbb95515ad7c97cfd3da63ab7ddb3582cd23f35edd98c04f9bc46871b350893736aa75d6aec048663f076b48eb5c5d0c9ed3beb2a1db7627103afbe9f9bd76e41a96cac942cd6e358379aa474989513ed2b438cc948894893f46bff54b19bf2b82f4385c32cb8df5b686d3e155337546a01c4713064d86e480ee70990bcf2df4eea4d544782f808da88a596d62180caf9d4ab74b12ca75ef648e36eb4a8b17ebcde9c75e2ed7a905b883f9d034a6f3acc3e0608424df6d365e1888045bc467bcf81154ad56e293d32e83b3501656e114b2b506a8872aaf57ff1e5b8729760f6a3abc4a732ddc0ae33fc1074f71e4966058753a7e01a8f30a84274fa4933140e805aa2cade2b0b5ff8f25c61b6d9f505a006c82e7de4c4549c46ca77024ef74645634a00114059827d7689cf8dace3af6a0cf3f4e9b3ca0e099299638b445245de62488d2693cf5a18204aeedb8f7a41bb66dd9880befa323032a05c66e20a1bbae1584a7b3248d1ec337cf5c2901af5b5aa12a7c1bebb3acda79e842ad188b59f09e2ebfd86d698162c8d0cb6306e06f3199c812748b235722106ff13c3d6b9185a0a8079c6602dce3789c096fc12685fd03b3a24071fcf4ea44182bc5894a8450df475b911e51da7fd1ae571741e0683a55014dd88d428f537882c97b61b3c9b9dcff9bce24507c8e36232ef9b1c9d01d18230f7b70c20798cc61436a616803bb0ea9e0fad394613fba5ce61861688c9b885bd103cb14cedf4c32248c1a528ae13942ece6c783b1fe672ff94a04fc0c34d70cf51034cbf843f5cfd0d7bafec8159c9e4ba2f63a7572c3f6f6c03b702d0675718de2551e6b125a4ab5fe0883c0d4449d5f49c8ffe9ad7a28ed30ea1d5000f157ff283445b8337934969745e679858104336306d91e4ed425902d370663c9275aaaba1afc38ca5409bb546b1f89ae4a9f6058ed2cee8a97d68ffc917e2a9ea0edc1315f9fea7ff6827141670e401a693708ba49f81227a1e091b3b626c362ec6f31e1c773cf7ad7ed0f1775889c668361324c87bf15a615ac3611ba126bb1b563d1dc427fea7346d92457c0532ec54f41b496274f6662c0399527eb25f4097a9e9fd8292aec95f3b66bfde7158a4d54a04ed0ae66d24486a8386f236fe0d5f909c513af3c9bd3cbd27e996ae0a273b2c862b9bb9fa28ffe2c357e0d8b8b20bd31bb787d8c78399d7137f1b435ff09867f9d0833765450e41700123025cf173ad8945c3363f9f989d3468d0c3e57d232c6cefa0113addb19b7e382822bcdfbc7a3d424c3c037cd29191e31cdd8e8fe71e496675fdef7f47aab584ee1a04da812dc18ebc378ed2647cf11f2156956c231dfc505763e4ca92fd86cf9c64a7b852c12d05d92ecced69b2f32b521aa12db591c7041b65a952ffdc481e83be77b4aed0d414c2a76a1f0dafe400fc644e7d3ffe63b5afcedda5efe1447429b77603d6b1344cde096c26f2130971254a679267c1f409a2bab9930352f5b860a5514e3f7313373fad430cc4b03f2ff5a568298728b4a7bd7f2a61f7cb63112701f79190fcb18e4fdceb9447fb241645c93dfdc805ad8646068708e64d3f64d97d5b9bfaf914ef450ad3f95a9e9908e66dcd3642c39519015922f43241108e912d360489beada035f1147d1bfba2f7c5519e24c157f2960c4e4ea799fa257672b6a3c8f0a9f747f154b779206d9ec41d5183109ca14084041602730e00ed060c5d5751761626853dc73a029ab90c19752dde42b5d484d99ac14674f163e955ec14f2c11b13a95a8e0381e9592a53610d948cffef8bbbf52d82581001adf52f10aa32c67e27ff13356edc2f9b742ccf8dc8db5869245e991a0dd09d3ebcc9fd720cea7846835952aca0d8aeae0a3ef9a482bc036bd27d730d66537dda61db6c509eff20444974b11dc30b4547743bce544182b461c76b1a2800ed36f19130bf3a946fd849874ae27fc4614f5c37889b9614193082f0fc521001440fe0f6c7b602557f105ca06d361e64f3854a68e8c3952b8fc3a0bf03acd3767d291760c18074d9e42099cf37065a6234aa625e04af43c6381b0bc5870310fb0607a3344f733932392e1a5d96dd83ee04f8ef3cc6a5aeff6b8c818eab9101676d210e08b499440e2e2e1a764013e082ac3bbc64d9bec0f984a1f8cb3deabc41f3a28a9e19d101feb1879fbcd5da055d85da1755b667b7a11040088d025c3fae7ebbfb042e556702f3e80ee6a71cf715f29c7cb6407fa1d4c1bde6fec9e3363b397afdb57c855c3bce86a23f7742d15d51c4bb09b51ff14728cbaac1c2271aad364cbcfc42bbb27a1c128d18fff98b53b3a916effc79a2305e14911cc1ad5d9cfa2bec4afc9024ec2756357e1ceb801e4cb7b09224dfca12fb5809da4725628f0d2101997f0dd0ecb156ef5aae4f1431c9c653d8248e48ce68057f7f2a3dad7c7015e18ea9d14b359c1acf684ae8d098515b25977aba15a9c120023d8ee3004bf0a445876acc3735a5bcd9b2f4d6ff975d097bda26851595674b1ac605b0dc17d93529dc618bba019b823a9910d2f1be971b0b0d6ad1fd783738975c0accae3f702915003ef9ca8d2f37f70489618e11aa5edf030cbbdc60877c1fd94d3d219c3666248886ef7c88eb99cb738f88713ae8539a5a019e9b3e385a35c3d5a73ce983afbb997a17ddbcea1da3933ae8d5905cf6e49ed17be661e81e4e9a7d3c9b778fd96018865c5a90571131b9244231510bfe5490ca114efaef2e20a5797d3ecf9e37c332c1de6077ab07fff931942e22e859af3e3014d7df48cb47ad8585ce57a33fe7eeb74b62adeeabbf24f24cf87f6f4b7ebd27c0c652d243de2954da746132b8fd2c33fca3a50e982c8c68b9e9fcaf9b65369e2722ed114f746d568367e4f13a0aec00f2d09e59590cd36d89345aafe9da7d88574a9363efa7bd0cd09c8f6e0a0220d2496e618ff298a694d75574995c55af6b78dcf1e09fcfa00756f09ebb4ff0e17926376e79bd80bf8bdb6dbaf0909809a96caab73f8e9f47b2933829e124e5dba98361b8e0ed54ba97b4c4548178f388f8104e7b714c0b645037891867bd006f01310b4bd3fb8e782fbc86ad28d71966d3d8a84f76b2a50b081f703933337de986655c1704aa077105bf09cf82cbd961d57708551d92e62ebf0c4b8155fe2a7ef0fa809538fb4459d515ef689b9de18e171ae8cd462203a36a2e8e51bb3919301df7f0647b20f7d70c748674bdc4e23e3fc23b8fada72830f2565ad0fb444e8c155306146b7081a5c624468b21ad10a49a76c5e700855e4c0ab6bff90b8982882c864e7651678ab0daeffeb0bb22643f9d2d494edaa75d7e41142f9acfc84a704707facee393bb4db4f053520fc4ece8d070262238f34b614fc10c411d8a0b16ccebba1b560e166e15919214add51a9315a620098db9f9b95ffcfd1aac1e820351603aa5b0e84bc666b309d6782191b4477d8015a3bcc8c321245d83183c9db69420c6c740997673cbf62854a98ae96fe6f19d3a2678dfdc1995e8634091761cb1059df57a551422e8e68e2d73b2c8f594b4c94cca49b6081c1b1418948061adff5ee21f8caf859a6f4a57e93492b97a2d9055f0bfa0327bf708c5c043969d1daef9a5e89d85f74cab5cd329087e70e29d139037deed6f4b4174014d2a0c1282eea8516f59d27188b00c12e31e8cf36f18583221a7e3061271a6396f40c6306de29fe6b5c0a7d2a0ce582029fe6d2f1910270c8e23fb5b54146b1f6d99a76b3d548c7825000777f3caeac56e1b79976b960f0ebe10fd8acb6efb095448ab29b7ebfd172cc33944907a1c38530e4dc936c483a342ba16911965e8a45d8aec1958cfe1e739950fa93d9448158438210adb8aad9498a2360dc4477655b651169215124dbfae36fd7e3d441b508fd512fc467b9692c340638be5d5c98666071324f874797d55c260353bab6ff7bc39e57523ffc5c91d2768db09f0308c6a9bb4a46b356fe34d2116b6ba9514e05d53f9f222462fbea97208b7b7e05a0e22f2638d597766d616d570d917987abd92230188aa206e52f553dcf3f95eeb02fd1f110383ae6eaedb2b1ac563008057619140ee7edd587f3fad96533bf45851e4e3d31a07a2410ca78d0c70acbd37d50b78cf33ef213a1eef995a0e7083cd2ab6b15b1a3551d0b8956033bab767e9a5459e77ccb16facc5af9e50b56c2bde7f5b7d04055cf93a4c2d5e1090d752a2e3ddf074941e28f045ed9bff5f9d53221a071ecefa4b0c1e858dd034e3d124585ad7ecc3d6e2c519ad5ef469646e894fe7f89c55e05d372e9aed430099268ecad2d74291666ce306d382edb67daa511226d64fba489e0ffe9befb1d726eb227fb3aeb47f762cda993922f8a322a7c8a04a744feffa02a50fbfc9380ecaa87d10defe17f4c6ff70be9a74b519687e5e1e5aa4ad4af18737e3933cb37e1d572866ca8f8b2b27753c72caefb88a7d5bf3df5c948f477bbc149aaee751a5265be2a7a0d6fa70de5c511c9bd4475fb654858a33eee1aacbd4faa28ea016cd72c0ada34b1431400febd0d5b8ef5b05fdc5d6b0f86bdbee54c7deb7a5642c8d6adae3597af5fc280e3b81ca9f3ebdae6cf368a3677b69674f4043cfac2d40c2b264b81bd8be0a712d84bd2d8e5788368c0106cc3577c065e6e83e08be59f378cb79865351d9dca6ea00ca707439212048f0fbbc462ab611b70d1c0c1f3e18aaadfdb8f7ad457de567918e6577bf78e14a82b23f712e4e990bff8cdd4a1dfcf18355afedeb119d178a4114b950bb430ebca1bb2530732c7dbc9350f567de6081d71801ccb22cb13fbf9014cb5ad66a72beae1f42a1b9b7b908ab1e38963baaaa8cf560ce6221172cecc4e5be1daa5c77db1dee9830e7dd7584d35b753df527fc86ef780bcf8448378520e6bc865c5b046e233bcbc06473068cabd3cdd5507df93e9f21122ed5b3d6e7c3ced565d4ccb58f114cb004ef6743b3a559733060b7704dfb3b2d82bb1d692933fcb5be4ecec513fed35c62a595f216a23bed19ebca14103f4bd8775f88602d52498704f5cbefb859ead4496aed530613db46c71c519e245c726a0743ec333cc64c6465e3635cf4b227790f5ef683cd033ff5e4bc1bcb2da51527f5fae2a72f32dd5dba93cc5f21a48dfa619441cd1d288f57f5de003a66fec46804547e10535ed7d9c5b09fa23bb73da6ba284152744e85ffb1a7757372f95ae2cb38be236f9f05fbc45f10fe0b975396a0150f000987c0a15c5fa6320e2b14f9f16e23f5c977f9d4ea084dd2315d40871f0fa216c301d555e412d7f8fb5f226e609b234bc9084aafeab609a8e82c24d150a33502e6e54c750e318926858ec26cd1bdbe3328c8203ccf897fadaf19021a4a5dae69f2d329947cea7a8069d60bf9a61db27a57b7fdd5dc3b9896838ca13977609a16fc1a0ede68845667284c8f84ed3dd947d65a758f4f9525f0e91f82bea6e3a13e3c207407151b04b7edae6daa528cac083b9e62b0bfa4989dd54a5d8a3dcf07286cb9bcac438fb3a8f89a23681fce678411379c078003239e8e4d06fb99d29a0800da645af326977911bfaf210b22325bba458b8625b170be7855f303116353e3f2180e49b9d437488e73cb09d75e39f8c161d168124252401f6c11a8ac0a6806d160c439ac663073640e78789a00cba4a9b56f5a161c54a8b8ee645c02dad1320a2a09e650ce86b7e9f76e5f1f2e1c386fb397d8d1975de5087296c59163eac08e76c50fe62cfaeea46e0c09db46c3aa40c144ac6f7adc23b95856d9bf27bf8866e76f9b6a99ff570ec8680d7981be9687c672ed896c0129ae05e9ea34701edcc6a5397854bf8aae06c201febb74678b9e4a739e5b36301518c2343da45e3252143377b532a0e7b7519c9e79abb73124d6e6b988f1404075fa5a21d8e6a289fe0522b32151ef91de518db1883ba8f26aad18f75f12739cd06e900ffe8796da86452848edaed8dba95c0529046613898b17bd23a9e0379eba3f7cd7a49774aa9b79fb6f546daa0316117ffc2bf4c498b6d32f5e50131ac06a7ff521dc330d5f9c61f37a0cedf1db9efab4ae180148c74bf3805f687878e867ca72157c027cc1f4319d1f36dd49dc64d196dba96ee558a7934faa6c371bbd83f247e71e17db5bc4bac62f237252f05a22cc897bdd688b20653e3df29557aa5a417aa41df4d8264bbd34feb31def8f300e26e914faf282baef089fa5b29172e6bebf1108f4a5685ba184d1b4550c57996e5c8b0f552195905ed16e1bcf6f64b458fb430f40be111dde2a6d3fbe25e0c78998498da81fa6db1c8fa2a9effc7bc8356442e502374f4be02c8734f910075fa0081dbdc2fb7d882d3b2bc8a956278a104a5e6dce1974ce5e07ff2dfaa002611e2ca4a3cc72b823d3b6b5d966de66c17df4f5f9bd4a9fe4c05dce8d4d18b264e59707e72698025657d072b8da1379f19e041bb89d7ca2aa400d53b62043a32381645f629887567ff5bc5e450dc1756fefc496e3b19385e059f22d7ffbb831f5aae99bbe2dead01ec46a9cae8eccafa92d36198413b6fc3cd999138fc5812c0a55840d77291ca3b93b14441b063ef47dfe53825b24f96fea6fc57dd57d105514f2858144796122f1f4de1c2728754b3b9d44a8674285ef1b9ad7bc57e3fe57db86a6f5b8017c78cd97f9766805dd5b3726d6049d88f3854a4e6697b8dc2efcbe67de6e51d05c0780598c4ff201de143d34bd0e210042b35b1d8da7942638807e28540479d111456afee8739603ed5e22b4d5b5a89e9366a7490f99451e537f6916d09a82ab5ea3456ca13d51488b0d737ba92cac34274559947b988b8347801e693e85209a1695f761295702c5e30e964bbd2b0863355c2c3df19d7c1fe0da43fb06f8f616d03aff13b6b91ca3c0ec045b7a013c11d9f5f534d9cc2dfaeb3363e6b1dcada28981b4cb203eaa05274571dca73a4f01fa9443823643e2d418fad3cc4fda4172ac52335709bc3a4cff44fbbf859ede70e69dbd502cff3284a1e29e850d484e6eae7176b9c7b1da5459339a37c7068f34a6fe1bc7789260c1e60d677d418bf95b0c885afc2241336b9e1168335887f0d3367bf843a630a4d0bdedc17e7ba0e02efc69c1a7cc612f56de9267b840d805dfbce37a195d80b520218dfc0c295437171a8adb3411cd48bd1f77df75096aa6a41a6f4791529784ef80d364b3f13bfc2b4befa828eb519638e4a22188a0459d3d32d8c2920bbd90351d2006b66c46ddac224b3d2dfa8d6400d8ccc503ab0cb31eb4db371f12c28bcaf71c8e85a60dc9566a39caeae93704961942a825d3baa748c2e73b9f5a5881f15e443cb9dab65dd0249fabb957f24056e33d1a90692689cf8d8a6269df071b95fe41393f97d747e14048ffa3036308c62a470f4fa3051b489b571cd54738ee9795df895720d7f8ee88a1ea0a53038f74be6f82af2a12a7d0edd7bbf57d30d5f81061c604c44b414edbdb52748245f90f874cb3abf2f73af79ea9a207465074f77045890505452b79c7c1a47dc0610bb4f04ab071942b9f92d9fd3a2c6a63047db8ed5eb5f755ebe4b5dbb935bf3a7fa2f839e6f1361f2d2fe4ef37812ad645f606a9d6d4c1511e3130301487cb42ed744b2c0f91815d7d89c9cc39d3b0475cabb31f709e7b2832081b3a2f9827f27e5c5a6a32bdf74822eda29da7acd206ac1d6ed9933e09c5f0c7b399f86474901d331110b67a4dc7c8999331c5efe4770f34a4bd1b72550f4172860d81aea426921268dfa62588cf295e2786c6dfd0f7f999c889bb44b9c6561101e6d9d9e7cd66854832963dbe785670e04900f64c624c5e57cc8ffd9fe919ca351be1cdbd4653926a243b14ebf439529a5c30c65aa19c5a1e23fe41344ecbb9b28e1069effe61dfb4823978b13bc21ed56cb63631d6f58d5db047eb81506002d822ae5af928ac25ad52041fb8be3f92fc231f13d4260854e2f063d5ca59281800e7ad59a7ca29eb72644c311b681acd009118b4a61eab4a7a4899eb838d7db89d18a6076c93d078dc5d4222772a4a107189be488b850345f7bfb549b48099dbb277e132a49f88bafe24d42baa543618b6dd599163e2729a626e778a461d51c41d886ff88def99a88140cbc68af0bb96fad89facf209018ff0fba7255192bd1bc821b95746143eb616f898c3b66c3542ca2823db43708f10020fcc4ece17c57783325061bd53ca51d2473f6a44e665f1e955dccdf621b0d721c665d7c0053858a444bd8c7c8607dcb437590db037bf380d4f59c1b62e3269c909233530286f182b42e77f05fc14c6b0119236d201dcbb5e468e6ee27a9fb8409f4599220d3f2402d48be4156e44c3ade606f76631309dac6fa3b07a3e763761b9963250ec685a2ba18ab9edb00420269648bfc7b1ff800940340c4ca26cf1ff6934a19541b4ac6188daa6dc1c2c03c27d83d7084c4aa5bf120882eedf85d2c7a2a3a009ae1ad5e2eabf3943dd14b4c051dae89ba53650ad042b64c9d497cc1f2d362ad30b9170846997b6c79136403ed619973c8132be9100216adedbcea90abc51f5cefa7759fc777bf3a00b0c0c4f63eab247faad4d75a27fbfd821ddd22458dddc6df77f0693bbd70d5d5726e75e2d5fb7e2da97dbc666581f576a7e5cf60615c6634ba4d11cbbf1424253354f5d738bda5617a4ac94ac022e3bd55db12179c27cd908d6dbc8dc193829b8ba748f91403bcc63e9dc484f63bfcbe556fb5790064e6a09d18cfa98c359783f25b9ed6e427cea7787169760c92023eab3afa77771e46416eb447729ff368d3591908bda5bd8400f97eb660733fb08ce6c24965d36e8682d273bc9305fe2dd1a27d1a77a489ee622afd492d69fbe75c4e580e0b586b398f9e75fa6d0f9352bae1b6b32ecc2ea71262dea083802311a81ba25ff2f0c5c7546a2b20904ec3e47f797ea5ede63a8d12ca13312dbf25161d8c7dc52dc0ef8346df80cda13954c2b6fdc6825a071972b4cf7cbb50050621f362070cd8b39ac8fc9a45188cfe5940b1fb20e2f0ff02638a286350177cdedf140980a1876a2107250f51aede8f68b700eea82d84d6550af25b9eeeb88437a59f64891a8eaecaf894fe95b48e41012bc8f085d53fa2f65b6e1e538bd65cca16cceadd3b66dbbcaf37c16de3c12f186950ef19bb933d2ea52659591bc093a5d27bff530d3431731563a38f18685f7b64f3533b553c1560ae8a35b521c26294fa0f29f92216c643eb15a7f505386d636476a00f7dec18079ddd576baeac7d9438012b99ef0dac3a42130ee2358223dcce384c08c7d3442c7c6def7f07b259f9f1a95cd6af4081f66cd79b09913e6fae7438e473b3bb6c15cf5886cc21a07c04df78e4866e7e7b76446ececf01ddcecc891f7ba9c94ff729a7b369eef488b4a5239c35fb06826ec3b4b34212f5c7726ffc7b54ea3e758dbf4dd2dbf80bef331e71903a3e5996998b8c9f8eb5c5ac48d118d3fdb469c772783e5906b22b3a54dc927746b34a338becdfede8c8c98c1f0fa5dc98b3fe3ce26553afd91660a0360a973ac3c13546a943fdcdf270185b537a5f67d7772505b301f1b8a009f7dd2fb9c14f2602ed151c0f48e66806a2d3cb785110e87e128c5ffd8b048b3a1d6b45710e096bad147c9aaf299cde9f5539c812b344a83da26b43fb209d7765ede29ef657831e06f799497aa0bc532a312d28e0dd5f64f381c5b712952375bb535cc757d81f702ef244764930596946fafab1aeb09bf74bed46c715258c11e67c2c4b018fad6a65d45ee156feb812ffd57d17f9574276bfe8aebb726b5b5c487ae36f12e1caa314cc3f83e79ff5185499007859fbe49d8ec4fc3e8b86e3a5a4b33c8476a3f406116c9a285288cc577db4e7f3a62ab740fba760c1f59ee03f562aaa3cd1f4ddcf816576e981d58cfe73ef4c121c240bdcadd0f60694c786a595cff11feab86345bfa8b85cb0bba902a85f603d9c7d25b84550046f664f82318b217ffd91aa35d9157df20d6f202752866f9f384d5fe9943be51f523b34b2d05b9d94865c9e9e4366fb346f4008911879daebb7ba1b3e22184be3bd7ca92a90d8bdcab3797fd85a711f5b4463ab63885a66efc28c069f8db4c97dbdf6ad772416165ab9368fa4145f0853dbe60b5fb13dead32c0475343c0dd29d2f7023050aa35cefd36a8ea7ba5aa9dd598180a1cfd68612960e91d494b379bde14b4a368378339a7ea395de5660b5af57bdcc2e9179e7b53b6dcc6608ffd3fbb2203839ac5f66360ff7741036549193a7dd8321d56c9fe555bce478c932aeb882f965bb104088435afe4e00ae0aca78bb7d72fe8a3a146303281d9e0d6e14917968eae8811006d69a8a6cf440ec12cef820ad4506d943d9908424a2ecc1bb075ac9c51fddfdc4dcf07cd336f00a71d859e17bdbccf9bb2947daba7a44f4af413dd45e9fbea9be88f1da85f0f4879ee00e9462656af513fa623a70d64d337a0e1522e80de593838cb62f477fb23cc954edaaa3cebb981dde2700ca7b3d0471e85db35aa2b83062bceaf229c7232adad9f7a245f49c86798fc90bfcb73a6980df6b228df60a925ed78a837e10c507917dab1e87fd6b32b1b8c825cc880febae5a2e72c2a2e78bda8a8fe399f624a9b2e205ed7702d9fceac2dfdaf4786762a66163cff420df2179901917538181f4828628cc77f7b2b700110f68293c56953c72b12f33a4f555aac13cba984b77a951d8553196088bfe80e36ad80d703db5254ec1591a32efe5f229feab34fa907effe73c32e94483721e3f215a9ab9c6b1d0731c0ec3e6bf9ba5bc44c42ea84ae011abfad358789ec04f481fc5c40ce9631d1686a89f0ccebb07aa2a2e8a5d0373b11be995ea1ade55984cfa58732aaa2dcb973fc6d222a7a1ef0856f5a5d5a06d1381f42348f54ab7d06de07790002f52163403d3830ca907172f0f179481815c6e5a415be75c4bf34a320d8541b8e083be15dd8a5214ce5a8b1c624b1646a82f13721ef4533f7fe337ae76b88ca773dee249b05c745d73c4829b9afebd425157ec3dbf703efef09ec2ce044e5fe9f73cdec6d16816ccf17fecd55d6a32bc291e774f530a9adad4c7a7e9a0a6e403ca70a2ca77ac163abbe0b8e5f6adf8715fe9460908e1bf4682b5bfbbe1168c87ece0a592107db465e7a5c7c1f3990c714e01d3a02dfe88ff842c2a848e7361918eb2aa6ab401fa5cfacd42df463b7fa610dffb35446e6c9252617ecc14757a223ce5bfc3f15e6b6e7327804f2385d71f8ba32939780bd456c4f8448d62a073be48a0727b785cb182ecd211c70fff059a5df18c23074f8de826e3fb32541e7d53f96f52a05208e94cbb650b591b373aaf417db08f05a2cbbcca6f05245efd3b56addc808c6b55f56827c6c12a4c353422eefb2e466b1a42b59e17bb1b1907b339e5b396400be8e5b6d157814c0bee0bab1a9a7e4c1114e77f32d5da09c26a960451515b31fb55e5e1dd309fc156ccfed7046598cfbfd02556216016217372f2e6060693d4dd0e787eee6a1d8381b0311caef1212616f9cb0bf556437d8badf96f94aae534dc9855a99d8e7705159e17cd3542007fbea5de8317ffc5719ebc916d8071d4eb5a7d7f438da4b4fd806cd349030e423e20e51cc08a23edc04f15d5638668f6b2159e3c1bdce7598cded2a03e2fc5d3c70fd1ec82d174c1382ece29b8c9fa7690f50b01de2ee3a4958cc9b7cf603777e3f62e4496b031c4ebeadd07471777cc39c54b87dafcbb0b659959fba70703e802b65efe36ea642a03fdf7dbcc6f4027dbdfb61bd06a53e2ac84d79cb50a2f8e5392f22a0ede8a3bee8a4e89ea6d45140b8296768117e987939fdb4a5526a7f3e6aa1c0e0ba1c39d5b744613c33e221a48de1e200a8dc1987517d3185028bbfd0d94b797e27da42599d1b91c1726af13af15ae2e72b8bfed7dae3e69a1c207b4baffb258b566df5d333819cd7bded8008066186509a07a31899791a4f2aec2837a9d60e6c97388aa11ed39ee78419c6a1c6affc6bdd21b2048f08e7ba5484bcea84eaf989162ccb489ea7371f827c36eb80894b975073358cb987911550f24a24f553b3cc322518d7f0d7d27057d6d8fe610804cfe2d852c8f0627cd25c30a7cda7b11b3d0ab2b09b45a1bee5eec8aa701d02d8cd67fefc5f2980e9d37455bf3aefc3395b4f5011ec0d27dbc8f76818e29bb4e41d12c719a62aabd6e67b47f7e7d73237c5c09e93f1888242f6f8846c4938c2a2afcb3608c52ceb7673dbd5163a51bf16bc12628827f6f3e6eebca78e3f8004c4c1dc7904125da1be4295bf8c91e1024bbaf5f38265c6f986f4172936ac2cf75f12a285d254044dea60c8fd767cd0a2561c8e83e90f1dca8652680e5ea8e4c792a934e451b2a13b96899eab090ad44c6266c4a24f57ebc10786dcc3966f2ca95bd4adca67cc7d830ee696a253ab906d1310d512fc2484b7f7e7e792ce26eeae7993f45ad308a9fe6a4886a62223579256b0a126038f6764c9ccbda69b914404ad2cfb197e40ccc243869a8d2779c5ea8fe086cac7fb8446bce18f636f0bd5fa7736ddf17e66616dc53b88710c4b3be7371cb264a065dbd88b751ab1eab9d0cfae32edafa192268f4d709baf7d739d902f4a3f641b2319b698b249904217131c333d3e94f07a890028615960f023bfa663bce7b44d790a103baabccdb28903491f583150232ec0f957601096fcfa7c473cb26cf50383df96c95fd92f31a77f39df64e136eac19b1d31d5c458f02db5c3a9510ccd9bac782e39690fa7c0b46e96bc64756984b9c460599ad25ce73935977cf09079b70df75e8d7d731f41a8fafa620693e9d82914c23a29195552a641d2e04b251bfec42d38b569371ccdd5c0b8f99f7b25fea32a0df12f9894cc407ad11782f9ddd9344a58722b8f7ab3e8a23b2d763c7e04f25bfdce9e4790d9db05fb0ed010a94b11a4af345e57d6c427c1eb8d88772052b3d4174a7d1d2db51f26d8e9440843cc19a770a3d46ba061d7a73fa6088836bd8bf096d7cb7e5deddbb8981434021d48c62115252bb6c0f17b8822c322b0a1db6b642591ae82a72704924731be0687cf0d2dab793595201aa4d6d458bc3748b0deb694a701001c76612eace4f61e5b4b1a12d9f4f1fa6cd315520cd60cdbeca17b81f32a1c66706ec1d1ca2300ecc07e43efe4a62cda2a0e62b68706be2a751f842ce523bc93cf18e7f44dc9670c4573061ad3301b964787e07d0decf8d55d7840f389f7fa29ab4473f03d3486c527487fc882994994325b57c60371d051aac0ad5d6038e54c76beacd53c151b8c1f359781404438e7c7906b440cadcdd089256172eb33da8a0777943ecda7f556e1183cb0e3744576a3804dcac5f745653ee44953758ae1e3a845ed6a0e82675c015b64420678a19f3aee70044273ecd58936fe1a6309aaf45e37688672ede4ba98cca0259009381cbfcb597720541d7ebc92fd3bfec3a2fae7a3b59ef29467c61f4aa614fc25bef8e7c86346b30aab98fb4b3de1c48e7f17632dec4af2c14dec9d98d96a5822c5e63f1dbef49bc8cf3f878380aacc7dabfba335fb7f9707d01f1dd241d55ba2c4acb35b222ca22215c8ccd7ffdb6b618d7fbf289fe5af2a894cf73630d6f2dea8b255fd0079b7c9a0030a5fe29d761e50e67aa04c3f84f9226627ff6ddadd2d6484dfa0205a7d9c63bcfce4e05ea7952142f9b49ed96cf342fb7cbeb2e34f942108dbb5dfebd8991b1f3486e09c109c9e11f47842ece797069fb7ae72b5526f4c4c9c18f3b15539bfb70dd3f3925a2bb252db479779af24c5059152928df0c8619d562f1d01eacd2ef45e9661b2f397a8991d91d16018fc2c25b1dd48c5f05b3f2e93be1c46fdc9a5a9adc2b21b0e34d7a988083df9cc5cbf7ea3ced837e5db8506d768ca5cbd8b69865f291b194edb493e87876b48c914f26ad2d903706715fbd263f3a5aeb35b5752841b33d57ef287caeb6084b18da845b66b494bae5ca622b545845a517c07597e894334a6fc151e8bf169f1c2ad9477d49e493131489f67e14f52bcfc68a6c2e2e6fa272097517bc7d8340a609a3ce40995475af4fe8217b7ebb42304a37f2bcc2673945fbd570353a420dce8cf4e7e46d3ecfacd3476711712b20c60c8bb52c77e7f3a7853717eec511ef795f1821c2bb49b56e6de85a2a88b650c0e925acad2b83f62700c42ae4e2c198e3de0418c91e1d4d36a1cf613fbd9fa405f762974ef7de352eadd80bb95393482d5875e25d34d8dfef66053d0732f4e7c1e1263b0f4091c43b2288ad2a4fb8f5c8015fb0eb562ef4680aa492055a59ec159298e5d3ce11ab232e930c0b7ca8d29e7d1dd93bb431247cc6d694f3c7e6995ff7c3877be6004fb1968f4931e2703edb43002c4476a09a8df6c0d5ad7b955c0489ca12bc8d6b64a96caa2e742eef6601e43df72568fc0020b8c640b8f5e6baa77dcec8eaf615580132192aa6d49a1d35e46d6b0b1ce05c29bf6c310efa4b7c288c6644f7b368004d8f5635e2d61d0b84159c7495cfdbea3ed962609371c8da66692da2b9c30482bb26c46f8484cc9953a376d312b443bfffb9ab46f5b9cc2d15e7ebd17a832af6fc889d87a89a0734c3c0aa9b255e43a1981de8238ce549f6d134c423008d7dcfbc1b68d999b0ab3be35631a1306dcb4eb598893baa4290b7e8e28da40c12cc59123a5a4af1dc13f0ca3c9f64cb68d763234ea180ec6f2080b3d7f815b1f04481696b3ef171dc2f5a1e56a1d81fd65818c4e69b28f64c5bc29d705789aa9f1e4998c9bbb6716c3db3d435bea8b57bc5d9ad41ff5c87492d707e4177711674add4d387252b3449f9e64d6d672149cf9c3cf63e850bc2652e9513bccb2e3b5a240dc6d1a408fac644250cd7bc065fdebc2cc12ce5b819b1a9314319f7221be274bd4c8122228936ca5de734e93007146d1316ed4710cab7da38341ab2e8f4079534a7af44967cd88e531ef08867438aa16b391caebaa2efacf02dd7582fb39eb6cc1f1bc663153635b80180d10698dab9cdb5f85bf629f3b97a449374ede930c67cf2d1245e761892cd6e562de2592e8c6cf7e7b428c6f18cebc6b7bc603c8e5870a136bb62aa4623f9c2d5ca88242920f7da77b89ee0ff532733491a52596d58e49f9ee3e4ade3c1d75f9bcdde99f29d1ea4c88656d7dbed8462108a6272c37bacc687b6f27d19ff90cf52f1b69467288ca052df5a70da271bf223afc83b1be3355812c5b8f15e8bfa3815daebb0f0c16cb710a53d883ab55e2106c7dbd19398aa7779e7457076bbd018889add8a75f5f7a2b0379f724da537194510d596add1ce82af43a4029f08a6297c13c78dd618be7083b984c8949436251665ab677ab2628865f5a5b64e5d6c91605354346bb091b1fb53fa4183b290dfab21d6fac03df26a4d162f820fcf43679da621c8b3e16ebaad9b27e2cfe55ff71d0de3c9b79aafc567b4d4a21f71d15fe81a13261825c5137b94c2caad6fc97b55758a1d2d1718ffe482c468f28b34b23fbc6be61135ccc187d2257745b35bfeb161c599683cff47c66c727b64b6bffb4dc536304d74d04a448a4d5fea6f1ac8b658a4613c57807ce7487a638204325f4a603a49f519b40ec2c0e2f8f66c880f840a5fa7aa2fbe36788a2f7f550af74b823a6ecc33abb25e42d9064346eaf8b0b672ffa7bc663009e919565539653c20fdcfedadac6dd0da3dd5ae80717a4dcf90a40dcbc59c57482feb30633c8c63c52d932be6ac2a33287dc4f5e9c131cc038ed7f8f19c09fc717ce4f1098a60ce1d7e239b000f383e1d6a0e74c8fdaebe0a9fdad772cea17dbbc329400e4c3acee3f21dfb781a4bcd5a6d33f11149b4d8be248488b59d94c53903af029eca675c66a90436a410b8dced30a7586a8389ec20642cb4b108e7e02b0461cd5dc4033f0baec43005aec5d9b4835e586502d26a0415dca122483abe6de16adb53b21997864febd1b03d8c65fbddcca5df2397edd4c2d42b450ac254d9c67c1ead354e3a3847d17dd9c58c671a620138c00adbdebcab60631564807fba24f3b66c5be54d251c9d11257857aa253cde87b19cca94d9f5a7015f83dc2e71af192097b1617bdcccd09298121215bdfea37f455289786ec4b6186cea7aab363a3413a1b07c740316c19c0af7a6e52e43a4481a710d554b7213910a58022f048a1b0cc6877f02830d38c7d5b807728cb226a9f94d381098d244dbe854718a59be59d041fccafb46467d6588e91c3cc39565eac0a08b88bdcbba8e24df1b9ccc0f071ca4dbc66fd2af0efc263a6731024eb3ddcfa54e246c3ad76c7092e051314bc4af71dced7166a2de4590280856c252d761d52d8b552adee90946568d08bbb5a873b97300dd17d0ebb664f779ce19df528729f676a82fad4d8e3273264b6522b60df721cddc5c17a6f06c22c48938f6572b1fca7692bb326e387d0784dbf0d66fbe1f2663b2a214c4960285db204151fb97eea6a9a2f4e85bfe9f629ca05028b14fea8b9e05bba686b6abcadcb3ed907e563ba540e4086816a3bafacd5a20cfaf05012acf1b90a34bec026f700967cb89ad7a8cded7721de229c2e76d85bed2652ae9e6fc94a1ed9625b61992309759f5cf6f5f6c6c0ffbf20d296b1b489d4979123b0c1d0a42ac63af6c953b54e7833db50776715070903bdbe535d1633c5a030a9cb48539e4a9b3d508ee80026500f23ce01268a8b84455d1a7a2d64c4669b8a8ba27f7be5effe068ad0ed2f9d3de236ce43b1d0687f2dd0681c1f14f37360fc3dfd688226f8d27b995389ee91f58e715ff8e95e5e2772595e59f54a4456c31466abda938bf29a807f908f053618ac4a94f20e6c77716e3c08777ce76564d7cfd317d126383b83ec96cb3562a4de3f1743c5df52d1e9aac4470b70f997b1ab07d5d6e5172ee72175a172371e79bbf5e16caad1f15d52b673e2352dcb149c1015aa3b4715bec4eb438be97145990bf99b199ba3584ba323c7c8422c66b3cd659d8d886419f7c8916afee994ab6c94adfc9c399f83637a5bfebc948eda528bb1afe0401577314dd58f111fd63c9ad1dc9a7dc8e3f27bb5a1a0ea6c9c92fde535397590b7a2c00419b0d13b20c418aab96c93fe36ec83f2f3f32a2c5f8950468c7e13abd985b72ae49e5c9c7e7b4b64399d46bd03d9711698c63025bd95bf71237d43688b720fff54d81943020051d04dd06d0ab891020f359d024293a6bac1773a70fda309d90114ad2d832c5f101c443cb4458ef01c38c3bd5217c4b7614333fea9052d394785da6a8aec316748cea32567b9e419203de9579dd52c5b242f5b796563b04e67dac72a5f7a654f4b8ae2350f6d4123d301b38ebf87ead15bf055e8ffdfe1bb5d2ff8c1d8903ab06bcb0b3a86eb864d8ffa74ef884178ce521aafc57e49bc706e231619cb9a2b5e095dce97f303d4649600f7b43904f1592444194ded73a48a6ef075b0055cb4dea6a1b869544e6fff047709881b13fd54f89b9cb5f90bf39ab6e37b8aba41755c28c3e0b4f9d07db39593cd34ba3ad7fcf809e066edd4ee3e88532c56e4cc6b1de9da23e74ea4cda742a03b9583cdf6a6d57a2307f8b4dba2fc3092207abc31493aea81060a877fe0cd2e9fea69e4940aad7da418c7a9bb0f958028cf9bb2bc4e248e000c94a03eace36fd4376de87672e3f5e757cb8a2870f5b66042dadfbca1ee192d944af617844dbda89b07d14152ebb7c6b9605298c3d67246b71f0a20f54f078b56aa9b090c7b7f9b20c2a7d845939f4c82860452cd9895a6840963e8f74a80259467ecfeaa7531ccbcd43f9a0f80227d4f16305f4291c41713024f02c1a5e26b0dbde1410524fa4e5db5530bfe7ceff76fa8800b8afc2e8a2dfde4773df40a2ff515386045292f8ddf523c07fcadfbaf607007c3c2471aa2ba58f37ae68666bfb2a669e0a3c4d2c31e8567d45f25493eb6f006ff15914bc420dfbe38dd59f8685afe6715b331e9843d7f3419ad80099cd5f3719500b714bc561bde2ff77421ee785c52e0de3be16595aed125eefe69752c7d56520b77425f8eecc5705361969e5e8a3d5a2a9a65b1ee80cefbee5b22709a706b19430a4744cb3be2273d0a5d71a95f883ce92469ba2795e8b61fc0e3192d35634102cc5cda0bb19c83bdcb905ec36c93e74742d8f52c579013dfeb4bb76dbdcab92ab470d106ef3138f766ac4bd41a00ae623024404b2b4022b48cafe93831e6dc7a2ce944d2503edbb743b061b20bdefcea37f9a263ab7343a3b4b2db6a205fee0d7bf6d1adb76f14649e545cfa8216157d0d37ba92646400c93b429f7ae0e972e0e021d91a2b7e34012d9f2fffd6216f94aaccfa48896d7970c76d5e2c0a2b946bc3d5794d9f71ca4dea665d7ae2d6b6afac9c2a318e3e2717319e2611fd1cf12f8172dbc4d75fe6de00b53945d10e034987366fa03fc31f6904f996f5bb57d2d4141bc45607f5ed232a24ae81789a3b0e89a037f8d391174ee17275b526b52be457859d9738e1eafac6278316d99884f26e7325a568cba528f3bac27c3f07de443bb9fda7afeca4bd46a4f693642899a6d8a43e14b436cecc3e9b0c19d99c69bbe0cae304b5035479deb034712f72f10fe1204e6ed1afd2deacb639bdff89406dcc2504f5426aa0da4757d474a19dff96f35c7f402726fcaae678d33b312b6121537ceeca3b3663ee45d27e3a83d6b6edd0bd21e3e1971345ea2e960c33871b2b81feebf06bc30b75b49a141a02f9be4c6f4be3c7138fee144db34d60cf80d936aeac08453826f98d8c5155aefd516cfcc4f32e76c93f9fc7191600d39af979e3c942e60bacb6940c306e5f1ae8f78da7e6d9de9d4eec0aaa5d93cced374c9e6b5e46e156a16adcbbeb145fd226d9284efa225092c19fe83b29e59c2ad844b6a0749b2b9e75fb582d66abf8e9c7416ba2cdfedbeabb48fc06ba1b9d1c1e9eef73a906885f8475c374b1e01a374f25d58d0a03f8a5e834bf8711f2750c989f6ef0b76582e5d6ef2cf0053ae01c1a35e1dca6282317a284145e476bf128e5ae1f5c31bd4ffa491c74d1da41c17b8b4df0e813ffcf0dcbb81da3981cf83a0b8bbacd23b33b50873686000908d65ac58354ac318ad7e7c991bc8223b0321681efd9bfdfaa135bec4aec6724a8a3524d37451bf92a01fa74e54cad57d54811d87495861732f5d590bc97e1b953edc4997b0e30d15378993101bf4305f3f8c335334499f65df0cc387f6a311b26695be8d89cee2c9a6d275e6a95dc79fb07c2b2b3c3c122be0230c6f51c9076acb830a0863c4a04f7ea8569083ff244eef9ff512528219ab67a2ec0a01401d482ecd9dd22787018e25e4d6a3b403fa94792cda21a65325a5ae02a48b8f6f885058938bab74280307071af9080d007b16929cd5f62231f2032516d23e39e52fad0efff7da996b3996f3e66f779efa4b5d0fe150bea262379030526abaaacf75fd3cec3b99bebbf9a5c24b8438bb1d07958b0b73940a2775fad2175e5ecb9d20b572d640f758a05ed5320cdd1d1ac7c253fca3e317868a00a08475fe4381edfc90987a71e82e019c6c9b5e3fb41dad027a06deddb48ebc40d3d5448007a9f9fea387dcfbf0aa3edafec1b0f283b15f6d785300f8cd850d0d3a8b867c5fce72c9221b648ab5c9c17c46b9ae1384b4b94899b9bd4553efbfdfa3f1e8ced9b7c7e1251c2439f3377b67544c46cc661a6085b56fa59f8e4ab852c7e554370c24454ac3b1a4f75f8de4937ce91f30c983522da3ee5b11743aed70101a5d6c6071cb3243e747a668f6a89b281ba403e9ab0a99fff3ba62d5290d5d46c924bd7c826ec35e10ce9218dc819ae43aafb6b3317219e85297818fb8cd91eadb848df0e343449ad8f0bf6177c6b33abc698442184a5d8b695d31117910ba5dd81d5ac8c4dc0c0a405d7e5f560852caa8fd09a3753d39f6088204f4faf3dce41fab2180f8e2c62d740307fdec9af5a002916a140aee4bf5612396f5401e9875855e501d702bcefc81034aa6be0031b8c4f9f50a7325d512af4530fe339c36c46883c37ebdaa520b8ce7ba23a86dcec28eb14a10386b7d127339c64bfddbe46eb2fc57e0b057b258f575ccb4623e3e19417bda0e8147adeb90021c3d075b3906c74e3a877216634eb3d0085a3ea5353482b12cf9a12c82cc4d13d01f29645894e0a32721751397298c83f237902d794e930613dfe18ce4ed8cdb4be7d834333df32b73717139adeaf148c156b73198133b56d788747769b00a1042e077d5e5cbb16d84b7e0f6ee17c8bcf1f8c14d1854a9b2a825df196bf55ae0e703a6add238fbea086ca9f23aa1006dfeaa4afa02511ed6b46bae39ea56e31bf839fd8811cebb167cfaa48be855b238d07f8e771f57d691a5c2f8ce52dc477a2a597fcc28042e14cb275a3a9557f3c59729524de77e660aa85676510f06f6fadcd78d94906213e6d834aa6780444701899627cad88c1bf850ce854ae98ec91bdd44cf5c86f98ba2f832dc4f559c781a9c7eba31ebe84023428af0a2900443ac120716aa9e0be733418acf47d11e53137ced2bbb9eb4a1d1d273eac467817cdb98426ff40063b916b98500e600507a461fe22ca069a400e0becbcf2ab038f3706b4f89c39029ec1287876aa12acb817b9532081fc369b5f1145041584c798f441286fef7b2d82a9d91524a585d8f4bb2d05e5fecb9cae815864dc82e16aa4988801cc2645210e0bbccc77455bf00384d3a63731034c7f4b070917a9560e1dccbc0ef72250caa682b0ee40758c1ebeb39657b29d86c6d10cebde2d053a53ab5b598e09f5c9238e776e0be01eeec6176cb0d66d5ef5bde43b0c73c585a67834aca9a13cfc9d28d202d7068a3f333bddf50b8611a40887446ee7f41f89a8690461bdfb161b19306cb00d842e6691937d5aea59e29e16d7a6fe82c4be38c6b328b9f223a814fc6ea4c13b65c0a4a781e44ff3d5b29087ef8f266cef54442255d8f48c64ddb851b038100972a31e080b6b914f78c83c45dceb442bcd9a5865f8efbe5543c2eab058bfdb6e7fb6ab6b1c285930d4786b40f6a6cc445b15d8a4b1ec8ac10b1ee2a0e8798c834dc9a618299b875c58d43971fe3eb0f97308bd23f14e90194d022d103bddd09368e710048516f00689822474b428545bb33da96fd72e37a149c04c1763a6d59ed71c56cc9826b1a8dca78b63812d4da23734e91d5d9d572ab14089ca67e4966625e520290cde66dfa59d9b8d12b0933678ace78065549d571a645324fdd9ec4e38fcecba68fef67304e2272fd5fb38220652a9174a02d2428029988d0f89fecce9448e9f9c698b641f75aaed4c5831b2a84df4a656419df88d0b78693b2b35a1993f5c265cb53fcae1f58ed4736945c2d8c0688981e1556832c0fec30e6fe6804caba5133bdc6f3d463fdbe8057811227f7a6bbcca42966505ff7bb1cb599616d10c77202d97ed5af9a1f5d746bc7317dcf1682584a08c9ba7a0b741662eb2143135e24096a0ff03d1c2fb55cb2b25da2e24d87c49692ec543242048648a39370c775d9045ffe28074f4fac85df620d5f4dfa5e354acd48d87a9060f1944f3dfdd8a945496192c948dfc533e3947ec84ad8bb20ed9e97418c7801a8f7fee1c04b49ea6679add4b55b233af15ce77e4065f6190824249dbaf584df211e25284fb0c71a94331651fec9d49eb061cf34260799e1c7de3f04bb75ff828710d3f1e9fb19d8c01e19d8ca97bf35fee1782aedb676ae6046b1ed55e83291967de3ba82da3bc193fab34ca96b695599688e05b09bc330593c4ddd898af669c215193c12a9b1ab4b6543ac0081942630055daf60bf9ce8951ab9f6d8e1e52ba8a9ccea02b50a95eb39a86b6b7b34b6dffcf6f33a88c356ab94e0a1208098d0537c5e29e0d3807b0817129ff30213284da4dffd820b594cda502bc964e4cb685a96158e7c69e093d1a8f8a1c629f29810c78690f470607d23ef3664f89a3a0f2d1147aa438eba1b52ebd1efb37b22a248ee744585f8d86c889c3d7664baccc417a6ad946568c08340bcff16dacde9bdfbe847d1888ae82a9cee784f76b2b948537610291b2b142c8c233b978e914162adeb7dc9d1fff63c1060a0e7abce63dbafa549fc4e723089b8e1e967d28d31cbe705af6f099c54ff3c718c91d58ad38baefcb0dd59200f05fe8115de314502244155af76d4fba51fbb8b2d8b7cfc783ec46c459d2e63236e338ee65c72bc5a7fcbcc80ba3455dd74dfe1f2ee6e394d2965f66623f4687ec75858707590d02f1752710f46a1e4439a6df90907a2d224ecb310b288f303eca8141b647632a17fccff64518e600d3bb6c4d65c8f0ff3507f691ed94193743499ab8de4348caee2d5bf93c6b9846a77eec4e09aa58c58f4b7cee0b6ac8db5bd598cfa2ed90e590cd955545b32c62e9d13db2bea1ad2522feebdee503fd2ddc2841eb618974c037afa98e2a531a6276133202234491ba3134c68c512dbc75277805221fd7a44170e8af1d9bf48d1672d3eac10483a761e178c327c1863a729e341e6f78ec1766bb176d19c375806ee88e61642d36355421f3cfd944c29bf42b22153f47b72d3ca9926eea6acb596c78b5b9bdd2e3162ae52daf3b7bfb8b36dfabf18bf315cf8dc63bbe6269a62a084ad8895ff1388bde38340817e0d5bfd983fabad3fec49011a991abfc133d6e81b2b32ab55e3bf7f3baaf2f193dac911cc9b241a4d17e839e677185bfd7811e4e490dd639a1a26ab03c8dc73af154171327a93aa06b4704f52d92fe9d3e9a390bb698348e3c2d6099f84bced0231d30d0b0055813a21a4f514333acb4ea469ff1b3b2180592354f97b012053a6432759f73dbb4bb68614993d3e4b49bfad3ea6ef12f171635c2f81f6045b90d4e827b23893d7f6ec1fc71888e1956e1034309c354f07f1ec99032ac743b6dc081a65abbeecdf92b0f354f14ce58c62865f022b2065278da94c06a112f9a402473462953bd5dabe9be81dbf279d258a3a508169dbf6aaaddeafaf2d3f53a9e0af66a6314bf0fab79ad2a79c9bcad7f3be2b1824c960439ec3281d72d053c5d989f9e77f8b49f4b265f1834d8260438d194c819ce8f25583b9f858f4ae614f3753667b3b37d7d064ba0e9c57a2816f740497a8eb50a9ac01ce7da0f2b24248d85ec2b53a62fbbca9f1c335d9ce9284f742c1762e5ec0e3614f3ead6361ca9de2dd30d101d25c34a30d690f6cd5adc33805627786eb91a359ef809775287b27778e7acb2f2fbc697cc0d6b7acaccf7f8e4f3e333477cfa5dee9a36adcdc10d126682ec122e53e856c2ab44581a23bbf45e848a9ad48dbd08124c935f0284f0ecc1d9faca35b551a787f7a45502ff94444a8f9e895fdaa2003c0f99e80e6e81321cbfede7549471bf31b31b0998898209cf6ca0ce658e07af5fefe365b1e1b972138afd35bf02b5d5961fb0aad0932361c969079198f71ec972bc3670fbaf8b129a1340fcd4f2da84764e1363a5d6fd6b1bf9762432c0f0afde67dc5ccee8ebc1ccc3cdaf4ce96e479abd85b46a659eb5612b924c7a56b880957be24d694663b1c71c93fa60fdf4a93a89ab7b6e48ebf85e4cb6f0e6521e53cba2bbfb2b1147368a8895dcc55b3159284f99ee135d975bd181b79467699dbf22bf55c723ed79c3885e765ae856fa4b2f76353ed37f7d2308d8f722873c8cd93d59558646423cb3fbee8c2790fe51354e159761f669c350cf9473fb283de78b1d0f5b460499d2a36cadcb2d1d4a5b23c69a67780d6004754283d5fe11ed1611b540eec1d4cdcb83c1cd66c53c028e44c3499bce9cf192970becce6da0b13235536a61af13718263fbbff77ddbb66dd939a2f02ca441e1e3336ee41c244c9befbfc1669be90ccd317a1f13e51696a472d2e4550b4b5a1d6e72ac3843420de276d9c97216b82d84e5f32f54bac4bc2ee4682b884b0b9e390c9355d6d4daba849487d93005e960e51e545438d3e7ced7f196d6c71c5af346b199474a4550b33b8dd6e6c970cb45f56d53ddf92de31838961d974fd30e90289d677bd23e91d910823048470047a44054a96b902c04c667fbede9ce604048a75aaf5eb796dc6f58b4400763b799104942ff40cfaf2b0ad38c70ac88529f1f852d6c57881e814eb7eca5ba23410c1eb9760b11af154c5c01836ca5462aac5e403832d68b723da7d6a41ea11308048053179668013bd0bf011fdce46a766d8781f292e5773084db78c94e3e3aed476095172c2a20cf9ae1e0c99a9c945963c74f6d23475b85be562a716cd47cbcfed051953d0993726066fba72a82591928f76bcffaeae7af6d03b24f2050ec4a7680d51aad9c72750653dc55ffc62385391307cdd82a40ac89e77e507b987494d7981ddfee9b40e6942388eca03e518495bea2bd70c8c9899c428a9cd87441a4cade4dd54e13ad4a88499d0cbf8385b57100bf2a68a23196b6515c4e4d56f0df18dacede8e3d1d3083b3d41f8034f9dd487aeb26076b9d7bc5815957be2766e2719a2167b137b448b440236013000eb87db836424d4256b3508f99bf765a2ae5682c4b01867e4f3f38a7adb64dcc7a1e8c20c47a8ebc4a0b0f278808b4d088e1295993b265aab6947ccc7e99f186de9ba37d3911991e4a64a112037bd2534d3843b5922ebdcf5db6b3473bc0a03032745e483e2553169137bd8de0130a76b079398f8d97d32d9d5e233836d3aaaa194a66a7cd0d73a7d7c1a0d15428827fb17071eb93f1459dab97a5eb0aa60b40aa97dc9034397e2c9633c29a42382be32f296f249ed6474540006c51789df63b198aa3df8f0a0751c74ce5c4f3e3ed5d92555c25cd438b97044204e36d6d5806682af49e319b88e5082b4a6d95bf94a2bd01aed41ccd846db769f03c0de74da1fb902485ea4527ce0ac744d1adbcd7ae52825398ffebedb9cd972af754ea524d6fdb4865c4fb4d5d5cd297ceda5ec38000a4494acc70b45d77c74bee07a4e8dd639c88782fbe44a0ef26922c6726b56af69e65dc8ade19528f8b08b35d0463a05e63c56b55a88ce452cf788405fe9a9d206b10501126d1d09ccdf9a82394d78cb7c3b0026c47ba442cb304bcc78f18c967a13b4e6601cbcfbe1647a5588dbf484bf504d1ade30f393d66a91142c7fdfc8fda3238ef7fd85340ef0193f26b304c2a5005db555669f2e3ca1238a6b3a4374bf60f89a13e0e552ebeed85c728b013368efe211add29856dbbb0aaa73b10a005c6ad6a4f252467125c31718a239a3a4aa12698c3a681b994b76c2a631d8c214ab6069ef0dd829c0ebdc831a16722517c8c6b5b8af6452ca616e5ffb52fc68e07cedf055c7f49d680455feecf101dcc1581329d4e70021864a972f7b1a5d08cfea2ea288a279437e56ab16309f2973a34b42028a0d89a2d7cb9b0a3b486faa86b784b7423d6788ee9d64fdc81cfcb04e01983e5cf3f4b1d06719d1b17507c4e7e5cec67fea2e0b1a0477e7f42d19229072eb6fb9f705aaa4d90af34679f2ea023cf6608cbcb7417a7fb769ef2e9696eda93545802421459df222e28e42826c56df5b9902cc059c0f689180237192c1f1302284155fd1bcfdd66797547ad9e59e95a107578899f30f01a20dc29b7e7d1eb33a2bbd4695fe8c1ed10dbd5edecab323fb7854f547298eede40d3b18e0fb5756b15ef77e1b34ff712e555dbbf549c51200b7ddd06dd86cb36c59b06b605f93bcdbf168ced1514c3f375b3197ee90e6e8738eba3f65ffdb25e3f3bbe548085b4d93718749c650423976ccb4eda0887693381d1bfada59d96cb42e79686770f31e058087ab4d0d6ba5c4dee71a386553bafc9670dcdf7f9348072b4683c62c8e2844e39ca647958fd7d1c46471ea448873c714cfbd626ffd51d336310e63f8ec1d8f649a2563432a606a9c44e9dfa72a8dadc2334a624b384a0ec3d759d3f726b50cdd666e94486d462067e7107b6456abc36b375b7953d51f99896182ebaaff0c62989a69d481a6ca5f0a7367274aa2509543bed6cd08b4d3906a0be07da06de960ff41aa5a80223cd92d5a6e12b54bfa1727a9fb91fad092d6b6d8adb5a5746b0932fb5d2ec2d8aa769435e710f79cfa815520c31a4200f31a10c7b06962eabe7415cb8fb18bff9532e5e8a39c480ac395b252a44daed6ba1f1c2dce2b2855b59bdff136d2f804ecaaa02a9777921d3501d0e569931f2016a08382c495dec48bcd7fc688ba0c1e6fb4841a90f37319299c427a3603f697c836d24138f434f2100cd72cf374e411b01b311d8dd0b9e885c77d7a002f21e8b2a9621a069e3c34769d0e2b69ed09d3920b20c093670139aa4296cc152c6361db1aa0476e1edec12e85b2758d3d70326e138cd25640275c38b55a5f63001c85c930bee4872decaf1203e8611cfcd37e38e4a4d7da8b5e1deed76bef0c28f8dfbd00e54a682291c0eead436d3a8c4f83a168557823579a462377969e93b536f64bfff04a60b6d7744945dadcbcd0133807cde93eb75214b81ccb5476dacec702b71e594bd7be042e09e8f4a6b085fea4d3aabe4c79532e3958e9f18de33e0fdd757ab629b21d020db727a1a14a10a7e76bb0d19cd476dfc3291bf8622fae41430d96b61f065435ef35cbae920b46947b02851cb3036c4e8739b9dd627b3b4a8692e2174d399b3f71eb2bcf20e46034b843997b3f2d2c3666b3b2feb822c3fae9ad9c01ce68e62ea26b81f50d1e4849cabb844f6ddffd7b32ff7ef6e69f5c683a25afc25358d365574177f524319e75da779de9ac0d758733fde764428d738d32983f5e0080303c90de05aba4f50d6eac28087731908892e544384607f5357939bf5223c643edd9f422da5aaa394335643247acdc0d306e05a7721299f47a9ad1f0379d176bfb2b13c38e27d528f6320afaeea38e63ebc15fe26181a6b70f7c1471500c887528ec69cbe0595ed14c99982e340c8003fef9b16ea594459ef35089499bef323724daa85f1d5d3b638edf1eb3a9ea97ed712e9a20159b4add5a6a67d402afec96159af3161a32ee2e291fa7747a7b52f350b06952f2f5fceb6390866f174b10c5f5cf377ac3a8813da9d621b2ba590fac3c0ed3f74363f15da55ee0f367ef9bb34d19f36d2e1b7e555375dfbe422bc8f97e55b873d975f890d7e265abf24d3a3c3486c07b49dbd459bc396683ac0e3f425bb67d6900086f7f8e9612f677de951de3333b62dee6b7b9e05f6b2a06db4d58782323d9530ce18248a8831f89e1818368363bf74409b1378edb2e6f479bd2446ae61bdf43d2c25da64918c586211e4a573f43c4169810426348528d683254513dab9c79f30ba8482cd266c681dcafc3040387de9014cce57afe144691a6ea4394b8555148d0b5b5cba6d640aea0ef4f8e1851821a28814d388a7a661f6f693e959f6404de532ddb07aac514427033d2d04ca485841eff6b77c49989df5894eac2c1e25ca7d159827b1a0226171ea2378cd6c9976559c5a7974b7556ead3e6e8b906d3f25d2ffe90264eb303aef01900f6056566efee9ce62400a34952f3f4de3fd9048c78d08b5becc6af29d606ef26bf698fb18cd589ee577834eaa7235a1e2cc63d2b79377ad47512c4f9da9f6554ab0e7d6468ded4c7883362cf68902d6f6bed96f18135cbaf573ec954098c2ee4ca44154ab13ede2616120e4c71ca5bd923237c3641d058e79cc7ed259795676f78ec7d7d31a6db566e6dfc0116cedb39084d23c4fc5306d91661f44d3d58bf16deaa1679182d62b974aeb0b6507809c143313b3e5d2d61412a243a0182c17b70d8f9b4c3fd78487ec3481a4a285ffa01bad7929d0a115f7e87b3ef0faa6e23ef1b2efba5efb19518ef27e486e595f80fb19e746244cf249d7dfede5e9d271399073645a75a7afd83522199a14a66ba3dba567a7c16bf7906b30f797f906e819b088ffb5a1e9f7afa0ecdaf53c20b181cc58e7e28e45e9fb97c7283b6d9a585acd3b964de5565b76501d019f313001f85425230e37abf50285bdf7ed4f043d6e07ea9359323fce159e27691541ec7fd6261e1104f7fb65cc4650494cec07f0679906129c441903079ef26af54d48a1ff654d93fa51683b537d0af48e6958edd0eb4b005b8171daadb461960b4380a18328606bfb877547ac9ef9899d2cac2a635ffccd5ca10b4444151604f3217df3b003a3ab983e7592b88cd516f90e5d9ad611633453ac68c4b9d3bd9897f20ff0fbdc312f8237eaca77b3cab6d65ff4e6b680c804fe1d1aabe2abffa60d999c27b20c7f1f6f5ddd9ae7be202401a0fc22a30057e5bde402e427b58aa30e67ecae422f8d903113a34f90dc4b18713480fd8d55c3fcbcb27a1573691d944758a62cf45954a5ec27a75dc48145c82ed04763c5a6d8dd6c18f5c13e92dc04d850d6c8116890dd703bf8df107e14e3e6fa610fd44347f862e388c0e2778d5b1f636cbbe1092c78bd9117ca6564e59f8b058c8901561624855f762067fd06047307da26d716917442220713150304a53737aae08210adee6934445e19cf06989fe1008792b5ee67a132d8f01d6696b8abcb86b46416464868d71bbbf68fc65351109b0b26559654c22aba196c2550f565e3adc9eb5d109498be82055df06867e3a8fc8cc9602ff65de5f7799f968f429b7ec8b1a402b67ecd96d3ffba285fa2dd4b5907e6b510179aed0c5b3ef83fd8406781e6eb5f1e19762fdf0420ef4bab7aa962697bf24d04b83316ac9792b14dc485bcf9273adaa617d5ce60f14307c295a2bffc9ca9c2158f30b02a273bfdd2717c49ba935683abaa7b0041633fb85eac7212f459206ee86aadb66b5dad6e0ad497102444619f9004d3970270611f3f865edea55df8a6e30ca839d45791e6c5bd94b3cd2533e22562264ad3ca61a1edcf0db171d557d894178e43e891e3066ca3ccfb92fd8c46a78912b08402db60a2a038ff18e531e20cb388c5c99df15dc8199bed1757c27a83b113df0c7c2b86cd42d3e7ef1428b4f74b0c7720d5cce433b45aef580d9a10aca8d5baf2c4ba7a86f3fbcf074d7b1d9b91ea7db644bbccabd7719e4312e1a2bbf0871f6409660543cc705b22cf8176f55a5e1c21e26b142e407a0682a1befb61f53648fcea82dcfb3ad148069c00f32810dc31d4018a780ae9dfd3d4081d3d53ecfa70726b6b54b1c227c43d1c925dd1b66323a3aa02f0fb21d9cf70147d69838a0d3e7d011ced82a84faa383ab3aa629cb3307e12f6fdf9c2b2e910879f13b284f1019b5afd54b5d62ba1d019546793795e9d8bcf39506d3b418b0461de04caea229389dab00670c3cb3269f44e89d797d6124300c54425f28c2f588f1f4e44f361347e0658b56d05a093adc330887c69aba358911e6c639b6e38550484c4c0ca45cb6bc115782ca6a1374572296ee0a1defbf2d70b53855fcef717f9e69fda794b110f1d2bdee2d9d14ba477f5240a1a38d6a0510ffb8520ecf249f2bfa2ad6b669946b0e2f924e951197c7055f3877757e0d7ba568234798dce86e4d40a43cd310260fca5071cd390906b64b1c64be478534cb220d2417106e9c29bb293327bf40dec2bd3523f02156bcc52d9c5d39242771f44e56ed9193aae51db7ff7cc5899c702edd0c67f5e7e170879074cbfb4ddd0d7bfbb73a576e4777b20065a480d224c1a49d85241ee4d3de91016c78781e5019394dc99e9f72ff96198881ddb7f98ff87d101122be043b88034da2902dd310037a4e55bd7d56e7aae5f335b7297e9a4c3b0e1ad1c154e5b181c132e4c25f8a530d011cad26fe8da81195644769f63a671e7e4c8bf7c2e48641bdbedbcaf2516928b598629656efd78fcda0d28ccf4564fde295af2902cd14ab001a3981af952040f12a49890a3af494f5c4fbb52d071b7173876be8eefd021b32354c80cf17936c470ca3192233a4eee0edf69d6d34f6e989675cc3624c19523e9f8f3c98da58af2617f7a63bc1e1e0163e37e9d22dd367d773e1ab54463827ea818eb98b372071fb97078909034dc34771664ceb330df655b87d1bee0346bfcc9ede01fb483704d278932cdcfe2328c6cc9402afcf6bf93d159d6fec8fcee98dedf22a706af3f5763e1f520966f4d32f16157129f4729cee0326776f343a597d432c6605e955ad28fdebad44ca4411cbea543525c72356fd68f7938642eb16839f2dc44f41e55ff832746e9db4ff385944e50303668d49da3d6984f0da8d9f739893d21f90722f7c3fe8c6c5f221b3cd19d2a0037c42474e783cc098d414f2d6ec1483013f1613b16157974c12b61c0f4b8ead80200605b8a28bb5967ea6012693cb562b2f7377a5711fae3fbc6945156e02c302a94777bae2d17b94d6187afe8d05e16285f48ee2b5cca8a335f755059f1885a943c2a06784591d0e754e7b3599de43b0f43dc02e9b2798de79ec87f78fa65bc20aca77cd28bd95b11f6fce589a57e3ad6847b70315992e3cce351f21a397c763b065ab2c4ec366d6ec8b0762c29ce4d6f55674345d01e8b83c5fab04bf21c9016067a524840475325eced374231e8e19b05cc2fb3139a4e16d8c012121dca0da104480834088396c8dce0a081968ed33bcf87a36b72c17dc2cda65b61b4610947a7b507b713596650eac55321480f13af0f050ffd1434f7fb310aa8b62ac038d6b6cc6b525df5d2583a4a24d1f41b53934dde8ff77b764c0096bf9d0b90ebd92945356a4f95e2de114370d05e5ff42abd1ae2f60e60fe3093c082e9b7c0ca3df0c3e99d6d5c0101b6a277faa8c0a950af0820648a21de58e0f2f516f817841f633ffd20f704eeac75c6056e2eafc701d945377932cbf6219231aad8984bd01d6111371cc5d05b483a844ed5679fc5b7c9ea58738ab13b5be3cb0f7b03e37b654ce9dd100eeb41d5a4308127872339ba96c7d1cd307dd6cb95d80f96a3084677305a8c49213b156844a5e3362b3a7ae4723cf4447a086f3f284d0bb7dfe79761e67b8050545b9fa329ef05aa04f5519e69c797aad0d5721c6d6fd0b422b0f8621845c5dc941cd7c3a0dee0365eaf327d54d711c4d5d63358112576eb998b589b6857e9f9d66409b2b303ee78ef4509a43194e8ed9bb241738648fe48a644f25158f082a02bcba79d7749727d1e4580d3e7dafe744c6076cdbb42a2e8b63f4e1a035e1a20b8116e1ebc01a64f50d0e12695028d03780e6434f5127eb45cd03ba24f47296f413fdf36b6fe5c7ee2d23f928d4f29c6cba2201dcc5a388f2a7f94b991d5cb2721bc20768ff938751541a44721ca1e5c0ef31984da65bc1fb04c28cf29b7b8dac9fae4872ca8302f011f25a4dcefd4d397782e1b70cf072542949fa445b2f859093ce0836f2566e6a85d782ea97190a240562b57943a5f05e26ccc22e609097fc289dad90a14c4547dacecb6eb9fab6c47482319d94579c38c9112996ef4028ecafc92f1c7c1102f5f0272af89145c1a61dfc0e34873eb4d7fc3e09a6e31a2a32545f36627789848fdd32ab7b26d25aacf8fd3b533e246c085435ad4fd33375b640d7285f5ecb3111d37c8a5fc815bf9a60cb87a921a1e48fa8372d104842daf307051ce29e964ad1cc54120d2b15998a4c5d46725d56dbf45112997a64557af61f755a5e279389de33546ac7525debd67e1ae4195f14478f376eddb420c7c67eab54e889dce5ec263561fc06bd470cdc4881f7838e97e9e4100e86a290ae0e05aa3865b1eced43364cd9a19e7e3f6f3185bbd06f8b58cef1795f1fd734d37dd41a08e6da984f6602c6b57c6511e166947ed5141ff0f50bac6ded1fb768979d87a8e9ac0f0614c2c9afc9e0e605b1b8fe8a1c732960f85c6f5e24e149935cd31c5b86886e8e9b4e375950fef1c813683767dd16fcaa24639db76bc961e6ee885292212266d847e15af48d903337ccaeb710b1980eb0533f2689debce81fd301a81a1f7931455f4c945e5c2c5e700187362085bac82b8de4e22023154f28f4d3c16908f5a2cff2f43f1beb0db24fe665dbf6967ec47d5a0a1ee8269c878544f70e02c7bd508210ada8299a9781b17c5d332f86e15076a931f6d7073a50631a6a2db11d2d9d4a63f3988292d0487837bf3f391294d0db547707294fa1e9b66a59cfbeacaa258af36ed13369bab984fd79ec0d87987c201e491b528d156cc61b1b712d32486983f727d5f1cac8f18232a2ba2818b2cee7b2f2101a721ca054e88442d078ce5195dcef14ba5e81aaa03002134b87b6ccc8966c310282f3cfb3ca8bab94bf551fb146e5110a4730841a444c1da510e44202dbf47869ccf719532de780b0a9233528a86158c865da264702a6c2262110dd10689fe0f6dbd02b725485d32f143c85b4bf898a42e3fff259b5835f21c7113d91f720c177c58a30f5723e667667c466ab6dcb43897f8356744ca8eb35d7915121d43bb5286fdc11b00028795fc82b0a89d14448ebaf8a2c5f873e5a99dcf3e7c80aeb17a94836adce74eddc3d0075b5ed13a709f8b448b41dd77c2cbbc39df1f3a835a5941e4bddd5ecdf8a2abc74a45a48d26b539d00eaac5ac3006e41ab329730959f92fd55f370e0e99cafdf38939fc4ef2c3f449d7d83e748b8d1c29bdb09e800cc4b83282d9c21d4ccf3586f484ffe80ef26b7b52b8d33c53994984721497811ac5890104d6552830712e29cb4100d7a11de9d3d006c2f09d688df3a4e8455a7b47ae8ebafb890197a6be4310255ed3fbba84d071aa75d3e7b1e5547aed58252c37d7e5af2871d30188c28d4ca410b98ed83b265520222055daae0771d95a4990209199a88f1184cb604d5f00b733a23316b11503ee05cd00e1a1790d40ded61fd21b60941563683e035672518e5f1d1addf4e7084a138c26220e9ad10693585110cdafbd30cb0b754a7e4d8811b5c8b3f3ac77d72ec3cf677ff83fffc3e2e0c8619e12e8cba589034f98c3e7f286bd6a100a4cf8131d9a2902764f64bfd5d145304171d5936375d077c63a29bde31a512a793f32abc6e42de6cccec77537437430228ff7daee7a394767b9f880bcd9a68937c1f1aae015377d121955426b76b589d048d0a1758610372faafb78a4b3b6038b00acdf7b057538f056bf3731a5eef3ce43b7b3e052b55a8edd63a38d5a80e367558e9f2bc613dfde08ea05f0baa3ba92628cd55672eb1121df9240881637da926a76f2aeee2f46f63472dd6196f727e504e36d0f98275675014841ab8b79581044b198102966b78c3e9b93ba93d9a4afb6a3c1678c1d0360ab249aa1ec9d118112de6c4bef6a1bdb0cdb5d0d9a7c2c308c88722ccf4072a63fdd3498b35fa020b1acb0cfb4b258dc462f15ff76458fa6ab1cb5f39cbc3a846db21976114ede7c8fae80de59d016bac1eae5390a9c1e964167ea4fbb551a7860a5551d968100d05d3ad05dffe1c2d3fc6713308bab068910661bf723bf0bd355cf981b8e8395a45759ca7bb5cb58fb5f73945df00f5d941dbd9d653aa744c3a11c31ff5d9d9acc68656018270adbb8a3fd9a131b0a379442731d285505d5c007324192dbdb4d3c255daa48d6eccc2663b312b77258b336bcc21e74b376e24f4b846ac2ed33fa576a11690877a1892f6da57c4845484514b3fb15270dbe0c2ddacdff94638ecdf8bef80c3bc07ecaf56ae5ff5208570a03635fef3e9a7098d7320f5490e6c323f1b1b6e5d88c27407cdeba78effd47400ceb9018fd8dca306a54eda3959f02ed14ea31f6b818f57252b522d3039de2dc5d42db23219e94e7259705f26c145ae6a3a2862bf65772276f79ca252c8b045491807bf023ae26171aeb29549a19342fa979861201455ab46b0dbc75964f1d798ac171523ed9c856513080d9bb598ef9352a277570940ab3ae53bca704ecd117cc0d2beb4dc7bbb5c5a45d0e3d870904846e4df2782349f1f09740f68186ed881f342ec40f5f9964e15a19aead14bee037aed48facc502ebcfb505e5da79b33027aeeb954a51fc16a23f52996982976dcc84d677c504a91a4adf09fa13cc783185d1d534f828d2b376ca3695eebe96677dd899987845f2446aa684292213f658396503b5c8caffe04dcf75980227b3eface2ec2c8b5e38b0853500d7bcb7c664bf23df6b7e22c0803b80cf57644daadcfa1376fdd21194f80bb6b5c844357ec6c9879addc46283a66503176c236c94833135f28f61a7cac2b7345a65483cb3a2023e6b4e374a17db204d39dbfffc71d4823063c72483b44061da51332ad6943b302fbb194a8aec155d031604b870a7575a9640acac8cc0498e93c59d0a8017cc1d790d43e862b21eadd462f161bff6b3e11d7b482e08e54426e11b4e062f73e5a14bdfe17b307c2044f60d25fc073494812ac38a45e48a2cd5caaf08664d8ec9564d7171e30dced3d058f7f18dfb919738b79043988e141388cd232b2c984787a09a952a34039cbf1314fe61212fc9ae1bd003297d10db48cf574e8264fcb37a39f32076d63064454a2d91d33eda5bc28480e5604042d01420eeb83ce9bb6de6b56154f64e23b0585dccbc292e8df6a4c7261283c32086c478bacbabcd614ffc055856c9eace7c440d16c1d9fcbc5ef2f2793264b4446185cab32d6f30ccde52bc98caa7f0da93ac9f0529aa6d732aef1e1d0ab8b3e44ef4d751df539534e1e03949701a6932909c57d29cd38bbe96591d90161fc1f01607ae34bd923ccfd8f3ef21189f89499fdae708505e2f44cb7cd2a279e55d350a6b78a080c2d853ed7b74d72c1e9d58e14419e7c36141efabf7e4453e478d2a879a8d28ebd2470e50513b887cad223eb513c724f762d441f738f96f1e22ee6986166d04039bb9fa4bc8054abe293f98a87fb4141fee9833c86d1ea09521e0dcdd70e039a696a6b111bf4b2788acb717a268902b723993bf88374d15e23497eaae178996749b0740e70fdfb2543fc69ce668c017a62830e4c534f2a9ecd0fbe8bc9aa6645fecb87e0a1bb825f887a9313a3c47d6b6aaf8a95d0d4d91dd4d3f25a322e020602d645d3a875073484052931a930e12b40790d01e1f9d8fefe5be152a4f8180df2bc3fb0541893e93a3d79a0a4e64e27d59b47f943a87cba4b67f526e093465be394734ffe61e748bf77c30ec2d97204c1dfca7cb2350f774894dd1430027d8b2ff491478e0f3b9d21de82e54b05eb8e71fea7395b416ace234e8b30c371703e07c3064da31357a2f7f21fe2ff77d9534e2344adc8f977a67d9b4b23515761689dd9559c87f601c59fb8d9b8a2596c7ca0e9b42651e79ff3c0b743dac33d85328c13bf3b136dffd5506f3e410d10ed096997568665c7b53232fb6b0ea8e0cc770ba44ba21d362d795f26833f9b1eb9c0b1d0f19d2fb8634dd4137e25ab588f61f827956264aefc75346efe8330787f5700d653988fb2a810ec7d56aab2e4096c667ad32a5b8457fec16503413879be9c5973e6a6ed11cc44cf0cf9967ca9bd0c0d4ea10cbfdee32941ae4bb67747e607abe700f6d4bb6d2a9b0506c37e49bfe91b91ab0e96ffc132ea879301613c2fb84a2ca47294c99e04de85383967b5c11742e4ecbed38f6fac38559d72759ca09b8171702e58ed5bf33b153e1e12bfa34559ad75fd939b6943ef572ede97ad169461b73dc0852371df486efe78c2c2dcbd21a7095af62175abcc4e73ca28cec27ef8443f44d254562e343c4f65a6120bd8f30039ba9f6e8f42d1ea4e29617ddc22bf90dd3d91901b167794aae8e1a395c43434b6757dab1d1061b76a5e35c38afa9f9232b673115c4606d2dfd33975f4a41b84666e32c6e9345eb1f20675417a4cafa2e2c7687bb1b4dc6fbb78a9ed01b66a310b555012888043693556ff2ee1fbe2f861dea836f879e847a3a73283aab53bc923fcd67eef1346adf45d65f1148b28e302e888001fab852db0c0ee0afd7aa15a00ebb80329dd4707f0fbf8f9bd923f711099c4a64cb31fa451244816edaeaffee0d554e54b41db5fe5ed9de09bf8be2b0ba04260f521515d3505a597ef3583fcea5be6f2164dea175fdec1c23c3a747defd5810c23c2f04005b2e20a94434be8528b35bde35602506ec4b31e131cb02a8f2a532577c267150470473a24df596c0c4519bdf448791b8f5f07cef5f2be726027749fb7bb8eb4fbf4c4e59b49b57c491dd05b135139e418e548819e9fc8fa56b1a6342e3588a8f9aaf4e353b977a6ef93e693dd90db4efffe88f71dacbb788617457fc9071586aa358c622ec34394fea2e027eb8529a6ae8fb81f1d941e0b8a4b932f147051928f170063f89a93450936d337f3f935ef956fda07012964bbdd4fe883956a6acc453bed9466d6c3d924f39960d28cc7ad917f89533f822af7e686ad87b74639d8d2f8756cc2170b95c7f91645b641d277c09fd9b68a893e048abd82162b7125af71b2c5cdc70429b324b2b1d6f13912bb513766286d4ea2cb8b12b3b77a39406cfd3df5e84250266df0ba3725d9c9735c6edd2566f36eeb0a27059c510f5296241bc739ad7b753edc5f86bb52540424c0e78ae4566d123075e533ef91021b2b19d8e10c4c8e97bd1e9f4aa06ad7e02a0a24f6b8bc526c00a6bac6047c9c5d0dc464ef95f69318c77199ae2f80e062e9c0f0c956f45098f558f2b923f4a79795cc05fdad4107ad0fabf63f86286b9144808af09c6b6470615c77e2f16746c4e8e87b8dc6ba25dd8fe1117dff704aa26722b08188bfc8254ed38cb8552b32ad11e53f47d70fc277d040e4a23a5fd92a5a9119e175a623a4e5b9db649c310dc2514bcbd1be778c2e9a3dc286cba22b927f933b4f33227f41ecf1ec2b04fc05b9ff7c5ba82ce71b6c9ee62c1d1f764478f908ca042c3f1ee2c6dea014a5cda2fdcd2e4eae47d7f6f7a4c0876c0e2552a27713a703aa40b49d005ed2d6f16167e7176b8f02d1b8adf1ce802a36cf762b6116f1b5f58b8a4c85002aa0e64483752b41c5aeaa5a9bd76351a83663cefb2ace73452c86ce41c2349ed2bd4058c477d650cc7ede6bfd669f5b8e5bfc8f637cdea906d7a89a519c45ef7a3d03cebbb832d35cbf6ebb9d8b87c037ead4063c815b2b37bc15f9ed7c7431a5ad24c41bfc8f2e42997965bf8ec6969a9fbbc407ed99bede032562d9b7b0470fbcd84ae06e7722d56ee438b590fe7fb570a2e073ea1ebb63fe116899a4d54978e3510e19fa2b488211399d6933e6307817a8cb46c0bac79d1fa07a7c4ec1dc2357e7f1fc8d0c1ef8c9b424ab5ddbace1cdcf61e03fa6105fb11dd84675ef0be76ee35e673388dcb183d06070539984683893e1fbaf008da3579ba9afd1da7913e9bb697b16db6870069e616bc2643e27769d5a02efd43797ca18b53140ba3622040d33f6aeebd39ee1c0ea791dd8b4ebcf52388aab0967aa78082ccc8f126078d4e5d67f06dab9ce013aa7688335a6b509c240b6c25c4097ae4478efcf804fc1aeb3118001ab977b44ebbad12b06f8dbc21af022851e7b22c74301bfe26f9e8fbc56e3c8499b85683b23bd5fb08e19acc5b4331f94843c099ca7d43c356863b57b371e16bf8fe6116539c10a4e8c221c13ee2b57a1dbf3d46b67ceb0816df829cd5da523255b1636a84041eeb957629486a0cf32794a06d96a5363c4a49f33cc6b1d0083ebbd063eb3816a297748551a033d095e1ec73e7ce5225598e546eaf380e663d28108b56be9f1c2023ced4ed9fa9e5358a60160fe27849f2099f1dc4dd5ea640da39773a831852358c2e28f51b961ef09a381c73770ace9a57445612261801230004b9ed3b2217ef831432da0706e60e6a710b9fe89aee77b36d07a69071e4b75563cee40769d57caf910f8a8aad4c8f261fdb862c7015170eaed34192ff64239855a686187d147e75ea60b6aeeabec4a5fde74b6633652c89153ab10babdc21be89af71baa523ad34f97932ae80f41ed848f6e4e4a22380d892229f8b0e5200144e696f62fc8af80f6e99882acc4b24433a133ed86c17a91afaedb07d5d4dc6234706bff8b342b2f5dd10e3cdfdcbe1476a2a25186f4f9ef5c77a72e66cc264f405a1decedfbfd5511de2bfc176302ee5739f74d6a91d98631c1b018b373184da220f099431cec6b16f815284262513d618d1a4281e8b75dcc57af693f57be1e76546563844cd360c4d6fde8d9ed8af17a049eb7ecec8524e31c7180e18e6ff059a7582a0f911814aa78dfd0febd59608b00f2a7d8eb61b645f9105c331b9931080a9efece6ac3a32e7b303e6bc3db9661306b376c51a79771ece4781e74bd04f4476f74735106b5ecfa274ad94e241929953698d3b35ebdfc1e785b5dcb750adf7269ebb0204f921c6ab9996399aebca4429bd5581628369531778d286133b7894fdd547b5ae287a23a3ae44251e4d0d9d54cfcb373f24a666bf835a2989a70b1dbe62d9f49dd0e3c637c8a584e725fc1482524fa9fdab2ad7e3610ed8ef7a03a1dad9371160839c671ea4f2be8b9cfdb05b5e508a076e6863cc108d90d32bb2af79d33e3ffd4fe7081cef51e389e33be26cc15c8779f5c91752766935ae5f58a2a265300535941f8d3060ebb1221f5e308637d1d45b5856bba7d8df421a8a060912fef9174bf1bb9125a20445643d57e43afecddf497f31985e4f748b62395336b4ce47e75c9ffff1516cae8a5d07007fffa5b9d4906b47dd2a862428018bdf0a61ffdfafb1f45c9cd260ba573e62a2ddad3e32a23dc7500453d6fa8cdffbf8aef82513d0ba7674e42139f44362a35001b265d031a4387321da2970fbb0118d89e92e10e3d8709a218f3cb0f9cde8fe3be51f7548a9b6f644fad781e09949aa740d116ec9021caaecab024915ba386e3058b5ac71a8b0eac882bcc47fec9ca58f131be5a1e86a1b0aa18045d8cf974c0d53785def4ef9bac77ff085ab3f3ad023fb156cd4d25e1f1f23bb46e780df9af6e8536eba7a26b60e678cec1e177b7db93ca66946e6161264896d1cdc93f25a978348636c9c2a74b840ab24190731fbcf1dae55b9baa3c87a1fbed525f9521cd30582203805decc1c206260dc32e20639a03e8e841b0c4a5ba183a8c2c3707a80f894038fadf2da2b60200e65f62cbbd852175a132b0dd5ab4fd76343e8a57638a3341cbac0903d4493de28dd7725e3418d9f982ceafe9248a6e11b049073ec39597b3bf9a830895b1750e502dd9e95ca4d1415c44cc0ed4ee3e81fea1e3997ba1a8611144ae9a9fd9b96283bfe55ed63669a08cbdc03f280c6daeba1f0b946b25bc2a773cedfb2ef1ecc0c02da8b43390693d69103e0e843f84c86f88dd090b6f743cc5cf4133b3256d298562eaad4fb39d7ffb52a42e6ba6cd68133c0342dfcba03768711319327b116ef630abd5ce6952fb7bd9035735a7759c4bc85705c79ce12864cba2a65219db9977c05cc394bbdd2e02ff36a621e2c9a05152ac4bcc1f1c86a46153b80d6fa98608db3a6d553aae726f5ce81217d239470a7e504a1d096d0399e49b66179f874eef30e9f59abd62a800ed4f06ca28707cd97754f23be729ce08ab33f7030464523d5f58429b101bd133044d2962e886aa61cd05b8c68dd4dbc059dbf7ac051b65a0e5700fd8c30f43adb99cb7578655b6654c3b57682fc429ee9f8e6dc2e6c9581184d6dc9f6222782abc7b796fbbfdc141fcea95af17e3a62fcb9ae01dea9d2c24424a99e7d2d167f4d160d4bd524ea82eda74f0d361050d68f42832e541e1a2b88c7767c5acce1109b7f132c1259e718dedc98a8146bc8057e9675ab8b1f49911c853acf4672ff6ac5a914562b6f1f84207b0b7bc9efbb687598b2a59dda9141573400a160e85a61a75b49fe08fad8e0e7bb86a08e78db92665775ca0aa09e2b8e39dd406cd0336b9cba7310e5ce8ff60e8d4cba4fc81e9e5a57cca45ffb5a0fa5194b37a9fea0071a0046fb85de22ea3e11b5c4caa7cd763e3203dc6638304a3a60051e15a314c419a909828487defeab0a4555e7c9db5b00eaf143ad886fe1d8087348c809bdb686ca7370b78ae60b7154ccd5f8b9f63fc7407b379520b4f5e82e504ad425aea135280563ddfbfc810770c124bb929c7ecbd581350abfc28eb9a224bffc2e87d99292f38ade960ab06b143decf0d8aee26ff9d4e3c3758c81aa807dd39761149177b4be3361b5e9a32b9fb2ded0de43e930ea487a823a4cd03996dd18de6d5916c941244e9fcac72874a1521fd97bb1a7f3ee4998f5afd9ed44c82ee8bf5eabcb67d11af1b39835063c2d478c2274007227a0794e5e2cf0fec09116fa986cc4bf608e260fa9254b25f6b90e698d2dfa168551715f3be2efbac6af8bb5ea8ee28898d1bf425cd59bed5c0b9411b7e9da62c4566ed43832a5a40b0b956784c387a49f616826e1f237e50ab6a60da9ebb9ac39c1bddce9a3e465cdd80dae4c2b42e2d3b9b106125bce8cacb2a5a71a21d31f3dd2bf97a9c2b1a0de74b99080d267ab7b7d8063edf9e8e2cbe5809494843019554d344392be357925e5875c4fef05f196692a9c77113b460e101c3cc7ea2fd60857cbbd1d5cfaaeb07f80905734f2d1be6ff113922326481b84faccda581ee68be7c8172236097057ca81cc5e0612f44f38a733c5972cfee1a69f763c404863d46addda4858b87bfb9dc34df20ed7a735bdff7765b1b7274c95bed5f2ef427d519515104e4293286fc6ba1f051914acab8668143b9ea70ae181b3ab1d2f7ac66b9c10874eb716c361b54bd13b00835760f67da4ead93fd9a824a6a0210fe053935ba9d8fe034616a643176702dbb5f6bd61824513b61b29442738f0e9f5c506ddd2e7b5bd411cc8a331ea3862fc5b0cda80de4c91a920bfcf8e89f6c2264407ca430d6e8396e202e29b3efe06858b4728f76d95dd39ddee3ab5679776ba0270fef15d2189fe834b7a7e238cc084f9c673ae0a55d87944b87960f627c2e52e6a9b28d01a84c9f6aef39c2ac2db232125fb2fcbb86bc885fed1b7ea1a4a20ed0bb703f8733c489e8e087e622b2ebbe19d91f713142b6be52c2ba9958862992067ee649abbdb1dbc963f308edff7862f74a37148a2ae95aae6283a8027b19f075b6b784625d2d855024cb01e2f4c81f9d1d1234b1ec3f76700a9839f75d87d4332123b1a4ac789783b3006296d773bdf43ea9b256915e714384b90b46e1abf1be8bda3fec8e403d1970079cf20a7563c74de05a6e608a6eb043740610aa6260c6956151e6bd80dc856b442bc1029c710f5926002b385b359050cf8b17eeecfa40ca0ce9c6fba4c78a0b65998b50b1774e94573f51deb66e4ac0fb8c965cc2468b981f7ebe85fb0bb54b637bc550e9250c8c9994eac761d9eac663a7f41e63fe638a80ff59e5ba4ebd13b31d9bc3559b74606971e4b8457a2f7711599ea7c3a65b2139a832de3d5fa6f7465efa99ac29eaba97d6593a3cdb3ca6380769864029213418ac6c7d23c0fe78822b909d71c578d04643624360a0575d171595f7c1feeede4b83139cccbaf8866a048653542ebdc73adcc5e5a850ef0d01570bd59a6f3c156215dbe85e2a0086a5769e8c2b21e3b50f83851aa0922305668abf0f5d6a313f509c0db70a7ba3610e06ec81a648ba68921bc02ae9a8a40be970b7794b189e799b6de5e12beff2cd91282a0534dd35d59e1339eba92b909d19dc0c23806dfdb88b374305dbdbb9f837322a1f0d119ef6221c26432991a90804f997930033218acc4225f339ec405ed98686a6d2df70fe5f407b6db9a6fa13c588d7920fb40de54c75917f5f5a0024d30bd184d1a9c7d44baa48db1f68322b3f62db810baca95046db2faad90ea61279ed386e7ee4ad50f118ef80b5cba088e8e52807a1ebcd7cbdb2e746b63e04e7cb8842d5c1c795c211d95966668ba925065c3d1e677122c50b28f344397007e8819321a2141d39f03bc29cf63d8e8616acdde0a536f065715f4a2e716221240198297791c3176162122fdf43fdec069faedf44ac1ffb30f7a826411a5deb60661477f16adfbe5ff5fe12f50e6e94c09c7d9e26cb2957f2f657293c27e872e40324dd1634eb260076f0c1929d050095288afb1cf04a62b894c6240a244857ce4887b85eeb664ac888049f143fab04ba9a477be65515bd8d1228e7dc32ecbfea12b976a2320abfc1099b731571dfa738cd6dabfd87b51ab6a91b8bd533d977879ffe6c69753e342ab758e8413ffa9c8b6ab0cbce22b540637774242b97e875b1fd395927531b7dcf3a3a7269ff5496827651ef7aab00ae96f839b435bbd90ea948d1bb4e6afd1a133dbb4aaea59fb4594898fda8812652459bf2407c83ba902ba62997d6828758c3dfdaa77d9b8b4299252125a12ff0a652389b36c117eaa36110ac0bda854b03db1a1c9e9a5c5e563d8f6b90e94a27888fe5414ad61b3de90b51d3beebaf600a388171b886b5b008ba38f6b2e3e157dd6ca0a6b0dc093ce7424103ab87db1d83860f35de40aff8f2ada4d4f5752647d60656d1f51d5c686289614231552a56765bbaaca83a3138d61a6fd89a234b2dec81dc5717ebf20314eeae7a10c1bb353b4649af4c17c1571c8d6334c34bc76baf13c9d809111d4d97d8b5b12e79884bc93e8169f68d1c2efc01a4b76337950aa2ce0361b733a6de17ef15aae3e6a1b61d59dda454a81d4b4368f3dcb0445912a1dec0f80ad91ed9d9931100f75eb0794049eeefe86b17e51be39d33f6a04f49776056e840d5ba9f9cb4a8fb127236d10d05c34b9c7f39c64528444a1135c82f072df54ad2a2d6e13482896c024a3694f17c3bd5223f76447e232905b2dfb40965c0be70601f267c6aea669b9d3a2efe1cb5eb78a834abc6f85586d5fc2e73039750b8c008ec21cdb8355dc4b6c5a6c217a210a750b5516a34e0ef4e0aa0b8bacd3e117a7dc62e9f7114c25741afdb23aff926b8d0331f361f5e7a6aac6c4fd659e5930afa7d4f2054950e6014b3cd0955db1b8c86504090f549a2f6bdb07bd8d54fd95dfedd575fdfcc77d714e73c5b8ab33a85eb5c89737b6e9218037787fcd2cc192e033b94d0391674ab1355570aa3d23c7a2e5c4361479bc38f0cd9c8d15399df301f3535e48f2c7c902f656b5ab0f618fc1c55a6ccd4843bf6ef1933a6d0c854523bc57ca990937ed88efb2c8fc5e0541f73393a2b14be4625bd256408e63beb726e95b86ede29dfd03ec7d5cbc03c24714758ebfac427c829a14bcc6de42ba634a6d1456eb368f4d83c1ee44ad540adaa989cd43f4886d35e2baf2b9ce276aa3e0b604770030f8e9e2fae980d47ccb9a482288497262f317fe5ed94212dc4129bd802bafce819318765be0f2ec73abec2448e4e3bff9876b60ad54d6df1bc28941e8550d5ac02381f9a3834edfe06758d68646abd21ba2a6882118840e92645002455e654e7902c906152d8229753054d4277118a47db295b02013bc4cf577c0a779ff23c0404acdd277f6cd7cad975b5ebbab31c52e7e9259fe4db930608f248a0b7183f72363134d31a00256fff0bb325350bb7fe80fb0e8b9c59de8880ecac1580e3280d70bf32741154d93ee90f237206c356b46115b319c17e692fa1f4d78c992ed1923b09b4bf784f87b935bf007099571aab1640d5a96981a8220a3e2f369fc30bbb02e1d0c5a61c94fa64b500abf8934ebb7fcda59ee3747d72e75385242f26fbf0fdc5ad9b975dec021a0053067008f24e803d57bbc018b7e701533028700e7ef3c4c8a25ac38533e7d140b68b46723f9b50340a60cfa2e37ad2a2de7165c4f3bf32bf0d467eef4d79bd259e37c77b4b6abe6c109aaab2a34274681614ed80a87f32316afce48a4046a0c3fe53f9571680c354d26501ee59ff9262762286717d648b1469ee4c6c98edca69125be2776e104c2dd18d116bfd7cba5952075b4e6f20896d142f1f6ac28c434dda44a830c5720f38ea7e35b41045afc9a00b75ffef58d3c1afe1cf06d64f84fb5db0153f29bb955fe3effdbce6692d14b29299d516449595ea29b933f35501141bec58618309e43d6fbdd0a5684c05a6d07243a1dfc0c51f85b276eefdfc0ebbf46cfb1c1c6e3543a081ad11702285f89f1b586a6cdf14b5a44ef360717242973523b301e83726b28eff223c1a0b3d266ee5f09ac0e266ddbca03986098b7cdfb0e642509d08eec264b04d5510e8476492fa518cc974622e0628d2254d757ee7d1311f86446dd01e698e5b86174d5bdede288a1f5ad14c15ccac5b5463ec644dc6d61a579caf983da9c31628e2324870b44c73412715c8860c4f638ce92eb2d4118d18f5149f15ba5041b680bb2c7a3a57e3a05d4aaf27d5cdf0033fed7409c52e7a0501bb0aba23167cba7c0a584298955b6e99e32359d402707a04e3188d4e84c98d7843edc869f80584fa1f2a897b809d301855b2f815c6a84569478e7ce7a93ff3a3c2e3f56b4478f9baffdd44c7bf6d7a0787cc26bf157a12f0dc5af8c61b6015e5c94d6eb59e63ac0150bf0d410ce033ae38e6370ad0d3faa315636a2d154163edbcae669765141a9acabc5491342fe55f1e53aaa8a6474c641723a84367924dafca6f4d9feed36b57176a9509154d473eaad45c2aa00b0a3ccd27107917340e7f284a994266d095096e7ac288a0d757d406efa410f4e57be95a124183df6bf52e8db509f51281d94dac77ef8884791b63d968d2a20e74ec53c24b2e5b95d8f3794c7d2aeb3e0b6cdb332e92bc8dbf026a1a52a51fa6ac25e3681ba45a00f90a89da8b819518d5df0a70a2b65f16e531100401eb66f04620ab23f46621ea32887d912621d9cd524096d937c6b517819af4002227f80bb229f49f32ad872ecdb3723d32be4c11ce2d3cdcfbaece542247d3e7b4e9b0267ba8e2d5fb10ea1b661091b5a882c1bbdcffe28599685c577aeec9f2d3af5e74e23fdacff30f9a268ee67271a58ec2b75c10e980d7ca0e72531e7782a398db4c369ce7a7cc367cc23a8e78a999ac63b020c8abdae0a80ad9c18659d94cb9aba51143db3602e2e0b8acdb055365876d41971696263b2dcfc9eec186478bad3b5529f698a6eb9c05e20d5efd60605da4a0ef0c9d8567429586ca8a1e5db8a93dcb35d227d98509db7b18f43c2e3ad7ea520ab3afbcc9b8110b9772f6675f2e3dd058ec2ae94cbc0fa93a1903faebbacd8129bbeb1ae180946507c981f5bfe37e78b9f7c9c88c24ebef606808614599c8ea56b812516f7e73df095f87733d6ba71051ce51c8e7d415e55e835a9a144734e21653e03bd38f0a91612de3130aecb3b3cece9cf0ff7e03d347a5eee1f585ec2f2269077633836312f643e58c69b86ede27037b537450b481cf0af4af8bb193b52ac0dbdec60745b16c4c760b39a9c8c35869aa4a5c365ef152cbbadbdf5d31a301d71af8258bd0c92df8e78c7665c346f426204f9bed69bdef9347f598ef9bab40eb63856762055bb43036a03528437094467764fbed6e5ab412aa53c395a365f85b843d9c0503aa561fa660ef4cf0330e82d1ac2f5ba8fa729582d067fe3935ae0a7b6b7f43b76bafe7aec722730fcacd44ac40519b8d37d25388c3b5c156917d01e275184a18e0d50ef4b74eb34d02bb2f11c23ba99db943c17f2c30c0785b65cc276bbe4a3373d01027311e18eac6e0ed7ace90c237e3003bf4c93512ef3ba1a3a3d789a213c7b61b9a8cf0e7bb47a101e13272d8db01f32079e85a4d1f166c5eb5c0e82644ed4097e995a38b5b9948c4d1bb092142a2dbe3068d77aa5137004732c96f6260f87337f8eed42a6e369be3c6ed04d3f200b8da7a4df3927e2b97c2ba8bfe1fb36ab53ce2f077bb595b7f3af796997476acf51e5dc55a27fa98f9d177e0c6c09f3a67a93bc5ea05f01d1e0d4cd8299728bead29f52d3dd809bbb515e9d61ace83ffc040be39ce17b48ed1d9087a19357d01a53bfd667d2c37626c1a943d8762eb7c73ce4b6ad5e0f6fad539bb5af413dcc77d1d7c97e5b9dd2de81c5d7b753f2718592d25aee2ebdc28c73aa1a150bce0e0f2f053517cc485793aa39ca59d41f3fa69810a3fa8c9cc6adc8ff623a7d997ef332ebd197026dff394be4dca249465c5d0e8fda905fc0999c6a46b8673ab1b3549ce95f1088d6f57300f842ae9e5992ca6915dc332f720805c5c40eea6b38d5d682c104c21e32bbdd52b6e8501becbf0a949df2f6e13e96ce92668d5253a04414765dd8cbecd66342d77a84543f0f446f7f1978d45b18939c24eee58cedb3e23443f1150add74348507320b611a63609f71e108f326871334cf9fbb2f5418634c38ce3b3df071097657144d95172747b70009be98585e42e0d8e79fad660febbaff65e78488349f5241eb5094f2ae0347d57e6411de892c0660bf4d7578c3a9a32eadc91fea0d104bffedc1114fe4f64bff10f4528914a21c3a828dd0d6c9de17936baa513e1dd571214c42f7848e90dcde8bff68666590219547c30594aa53a752960e4e08756654ed742729d0f927d72d9490b244fb6bffd6d4fee837a1ca187221288b04ef9420637ec5dc4d5fb7e1950fff8d765f27dc066d5641037aa1d6b129db3f2ef531e3e34f48415eb5fdcb529b751a54d79fb86ed1349c04f8254b315633aae79b8f27b6c7c4cebd0c3d17608f24266cda67cce8db8f93ff10ce3ce6f54803b4c148613b4303a62385ecb8a08d5aec1d7f8f2e5af56db39fb110edcdad3f40c6815d74955a7691b36ea1ec89a59584ccda4d925e5a0b08b187daa073f805e473e041458107a8f323893f7880130e64c032237602ea491b6b78f335dbdb51a141d6e14795feafc9fb90e1e0024c6e4b30e7a22d2f213e865eec56c71fe620d47683b9cc7b9f28dbac79316026de24bed32b72b6fa5c6df01d0f972d4213070fc16497e42895fcadfbe02b8a063bff215debfc6a16dda268e579c3acbe58b09a80030b9d31cd01ac3df8db6b676163b8520ddd4ed8f60c55a9a179121f56f8e155a1f9258adda5e2236ffe73b5ad8c555182c00049f2236d101614be33ef2195f85a25dac5a20340c167207a607dd64803a253ec28b64561fafd2ea3365dddebd7f41461ea4805da78cda601d069305aa63529ab6386a1b56aabd8c49c8ca2d3800ae183ce27867adbbada629992b554c12007c27050d5329e23bacadcae9844ccef76a975679f5caf2b220e335b93b7dde8ea6f10dfa6694415fda69ea67933c0049b6965ae23ba635733ec026a2f7c599597082d5164394a80d39ff2555d903bd0dc1611b9ec8744783c2ae9e8fff8f8ab7580f34d3ea811c8f84b4fbf155c31ee777be625c4adfa34292f9ab131a2e692de6ce8d900e203883060346e336bbd842c2debbed73a457f14e17b8ba9ee2f7e403b84967b684f24042c61403f669d8b88d5e5b24e44e98cb96c1169c85cfde4302bdd98e0e9c6d9106286d63651e6b903beaff49ec26590e7a29f4abc631f62005706a74e98b9534d93f8c43d25856dc97d8ff5480c1255358abaa8762e621b4953d1bd8d3c127706517cc03a1aebcc432b6cc3e784664464abec270099294fdc2d1252dac424839635661d60b7ca0ec7e877d38c1148e144caea1b24ad870fcc89ccca6d25f07cc951d8d59c689c3297c1cdb8b6c1dc2af0c8900a5dbc55ea641989759447d6549fe1aca71c3a0d90654595bf9cee6f05859a89423a66343e8e2173eac5d007cdd5ea146e3a22932647816e47c1ca40f1d8846f679f5bbaffbd009049944e3f8946c5aa52186fa66c1d0c3a52b59ed5a5bf85b7f862ee23097fcf6b784ff0ab4291add432bf15c45518d04ccdab0b7763ab2109b8479e66533828817e8cb9d34fe3bbe2fa763ff1ce9d7d726636ec127c5e371f88595f864bf2801e585fc66603e3eef43f344afd30cad4c27583803e6df5fa8c94180d9a07aee3126ba9a3c8e5b54d871decb437d8bdad82b20f8d8657bdd154d017940b4edfa93ef1abccdaaf6179efedf5fea6caa05b02b5898050ea5650dc70ff1b3c82321fb816db0f5f25eae1d3a8960aab019befdb61e356faca49c8598df4d69145623523f1ce571ba8144962073924dedea108b4949a1c6e01347303fcbd628888ba993672ad79b183e2f5b3d6ca78b7e991198ae75b4f6cb186e52a8cf5bc90e4ae5546790593647b507ef5cf6ca76e29d2e4705b4b22ce5195ed041626382f9a7e19cfae76e4f7d6202f398a51d904b1181a03c34a8bf02585ae8224f30eb39cbda0df8c8e00f9c6684464f6ad98933bdc2fbf9ff6d1665fc6894bdc493afdaff60e30b9955799be11a26c67791039a1a35c4ccc9ec3c0b071deb51ccfd8db0be26dc4278a99c982a1158ea01fbe889909cdc6510a5804e049bb9d18e5a9e6a6de13588218eefb50562885dded342cb584d86566b1c34207474faaae1b8fe062b28817e14a8344a7dcf59c2fc58b864bf86c86c662e6ad45183bb3fce237626332ea8b2220c82ad562b444fbfd8e1eeeee871af2b41223f39fff47e886a92059d31a601231d6bdd1d19b9497cb97fa1fdb486d5ac9642e704b849b3a6a19cf1799223ce3e7da092ce3cb7c455f09cd4a1edaf429c5f948cd701c0f7331c3a233095216a6e4974e907e0babf113a5c4d26b9052d06e877e5117117d4b255987b35f87a6334705b888f1848386e5ddde30fdf8242eac30337809a5e5ed30e3f573dc50d3a0e1d3ec91e670ea8961d6cffbdda6d1427f7914495129dc753423ef8923997453afb0ba1e70284ee6cfe5a444fcf2cbcec4749c605ea2d0090ab92d771910021dd2297a1ad7a8a3b0cf9442b26dff1eb6638af9c216e14e1ac725ee74e89ef1107540d50531220d02fc5b95645dfc5db7d95ba0d6af2f45ab8e1e1004ac38c9787126881747f9df8c187e857b70eb1ef29997aad5c696351f5c61e740faf9429e8ee298da313b32e264afa796103dd7c19ec0c58f588c2563747362d5614553946e9ffb25b08af276adbd7fa3a9adf3d385fcb175b304df73fbe92aafad1cf548426b41849c9ed187cae9d9753fcc30ec8f8e5185c902fdb39d73e60deda8de9393ef316814cd53d4b8b475cd3e1abefeaad3ca47bee6d271e78bbc7a468e11315bb891d145092a137678e0fed0eb8cc9efdbc584f87996a36033d9506427afd63ccdea69f8b5a35f193e1d832d147e9e2f200a53ffbb7d893c4b767e3cdf7269df9077ec7d766c2220d5d7baa763ee96a5cf808df6c473c0f4063770aa255bb1e745dc839535b95cbebef303bafc8412fd34fb78b90f56e2a0720bf27bd248b02a7f42e625111b7219df0fd70858aa5eface494cd3d0f2f334d61a815a5006e7cd7a76512cfceb6f04da738a51174af0b4a9c7b39ef415ca9ecc0f7e62974ac412a982266b866d4cab1c7a538fc0c1a5ced22d52d1377181f4e2294132e48fb68bd9c4d718a06293e7650b6f3419bdc62fd563d33019c26f7d2648f6ac64bcbca2e4e1f3b085307c94db759e3174c8c973705a6701e648c9bc454082c5fede0e1a821cbfcbafc2c76aed6d5ce47948779b54547a879b13e659263b5b36b987908b9514164646568ee3247ebfc12fda8fa6285c88d339baff09098c0e0ab8098101fcb382b1d74d47652dd4e347ad756a84b7da6876bdc953e9d3404913d1670fd6ceed18e47cf8df89f99e31f80de07a5e9d5f74c0a1d9ebc6a3bf9bb7e2c874b7833b0c21e67fe3cd2c6f2175d3f81b03f6bb8f649ffcf8d7c7e9ba5d460ebdeb32ae5993b66adbd33e011b55c60a198733906ed84e5ab2ad01a1c3eaaa7be4bd21c67752242993f854586eeb308365615aa2ec777d5be77f2f45337413b1077924fe1302bf872636a08058b9b681c7d70d53307e011ddee3f78bd1f24cd64496061635a97a83ef342108e823653e1ca56e5408a17e2a4f7a44736f64385f232de3c998e6435c085e89172ef7802326cb013194e405edd5b967156c2c738a8ca976785a931ba49204949f5a466a4c8f80dac096685956ed4c90ab0cff03d60bb1dc179b467c227fbabec1713b38cb43cd0d6d6c547e6c7ff802da7b96834d3db077578c0b23148a6b3354f1daeec08408623d5fbf8ba10e4c609ee1254430c511bbb82812a9f7420a00f7bb6005fb76f1fb897f9180ae597beaaf663ca36e381b2bb6af6b3c2f572583ab063153f369603cd7fc958380a6d4ebee18d897ca7849a5542d5cd10b01dd07b847621ff0d0e54f4f19f47f0070951fe066dfbb106af81ee23360be8cbad5e95786f4d233269e09389f6f58f1b8bac255e6352c8971ef35acd4f0217e1d04db13ebbc7b08e92fd4d1c3bb659e880de93a6e9a4f505537c3a0b9b62665375424a11d38c68d98a70c402c5731e6ea661b34b66da26e582a2372f3fb594f568d38c102dedb0af515091eb8c3bd0b19859a380c58a65d1091729b419420e4921bbb8fe39332db35b1866d5ac5f6a2343cff16763e85ac3170e0f603b32f246ea558684bdb249b18c2ff50a7b2a26f3ddf9c80d44b64e442b915a5652cad532836e4827a59c58dfc43a8f3a3355d904b9c1ac6726f8fb89cdc3f37dbb2aafbb3abe0b7590343197f9fb1dfdfdd5e54d05f17a7b86eee13130af6516cf3bc96c15ee714c380d4ba3c4ab53786b169533e2b7fdf56a6eb0f6408ad415f1dd04cbfc27d370e5878770ce361abe21ada660aa47b272fe6cb564fc645c707ba76fddff4dcec08de28638afb5268b4864970620db13cbd5268f00b1457f95d2beb0d2ce3ac3f62feddfa07cbf92348b809cda01717a3845e9f2cf8dda9917a8b6bf9669176f88b27bb3f58c52f4a20edfacedfb3469f518e159a767bc12277085892252f9d5adc9c098e79bd531586c386fd5f66c2577c2215d20802213367ebcdad0676767d91ba3ee1448e9298413877e19c4bfcfbdca38c1256ca7778f46d246d08c2ffbd2d84bf76d2e3cde9c4c1453368e083d2985eaebc26cf0917f2d5342a5137ddd8e0403c5e9676519e0642161b780a8afddd31510e90efc24c570b728d7717b865716640dd926b56cf35dbd471a6a373f3f46cc638cc8c87afaa49e8bb16138f779368f571ff60087784723c0027c310273c1e6ef92dbd127d88ebad275b931549ef0a9797adb63341624deb0026537c3176659e8663a8af18c8d3736331dc59790df6ed23af9ed764edd2552e62fa7f8c967553609317be6e8f4efe687f8610f26eecc148e522161391a7e92f160ec5ab2d7263eddc18d57f48303a7196c591fbae0ff67f1b52489a2892403d9ccd57815928927cf441721f57a387e5bc180d29d3aee8ddf9b4c84e5c117579dfb8c706f07b0253bb733a0e69ab6d8dad19a780e056b7886dfca65058c91c76a87d72f53daa626b8a85c161f2816b006b3cd7faba5a9a3a237beeb647a0a55e54ce66ef69e79de88555f706263295ee05d05c652d6a0e0f6d9e51d58630caaa21a1f592c1e082e29b4bba66daa177f77f7bfb7e49cba7f7b992d8f61512d3c9669d9058c6dc8aa806a525f7e804518d909418166cb7cf3a8a9c91059c1fb4888a40fa39ef7e782ee95ef78ab864e6882f9c0e23b93e7d9d3b0a395d9c150bd6b149442140e7406ae0ec6d8bd25254fc7883abde0cdb59bbd6d5173c789ec4083c23d9ed550f0f1de70dc014ad35b7e9653a6ba5380e72b1021755d3ae6f3f54c20b591d92e2f639e01a86e9278dd475107ea580cea5a49026e939c32c18f38e3826e29acd509d303b8d0724b0f70ca7f535cdbcbe0ed9ab8119626b3ad4dc3659bf27f5a2bf9688a5b21f9589f65ab4637899ded3828d9222d8e7a1c8d9b0d5677a4db7e84bb835ab8b6ba80875a68bd846e578bff62f9b02c69d8cc31741ea8f188860e1ace3ae9262db92f6365f2e55ef0455bdb6c54e880bab0ddf6062b48d352f7290d28e04d65f7e85349694bc1f670236150c9ff05bae71b8f0a0f93a2aa5e1ad63a471f82627ce091bc30435be4f66510c084f8127141f81e2ddf51eb7ae988101205d1cc39c4befea74c627a2da174b1baef0a8cde5b81197c54556f2a997f1c62f4fa248a5b9af50f6dbfdd4df909ef7673d805c9125f47d6bea6b0ca0e3c3c6314afc05897b31773f11dfcc12ed75469e119eef35361b77d6e5e89022f261c05b9e954790c4f99ec53e2b782a48a51f8853a78522952da75ac9c34f9053f3abea631f214de4f988b8d677ab0ce76645e4683188c52f65c63f8e316b29948c61e79e7c9a578b43afe6c7ef748a719208a085373b2a914a60c0d788c496b2afc058264d92b4e2f12bb7c3ad571ee3996d10134b65933582704b7d3c0e67d61fc15e2536a0304201ea185fa90b08c2792980c958246aeae71dcf9cdb8a687d1561d5329ef408816f4ba87a1188705fbfc37ad130e2d7d9f279e47e6b68e76914b5d6725295706dd65618ead83c27287651dd5399cf7ffcc2eaacaae80493f8df5002262dc3aa3d52ea9bce25f969d3b6690f29951d3ab0ca754524e4177057d6ac88cebd1e03d4e83ff6f56b2b9f731c656fd5bb5f43b33a9bfcbe5dceadcf7d96e8b814071484694a83f0a3fc391796993cd2a8eb04911643992ae3393982f426060e042b48da28bbad4563c799e04574c0bbb9e0a8a276ddca0d9b71d03aa54142f97ae2c4d00dc4e758d03a2a3c1bea2d27cba0ed2e04c20d70157f85542a50068daa82737c5c46b999cb702bd0439f8fe5ced4a15d1d405861d95a592cc46ebd07892603c5cb9072702dd331fe2ddcb8af11795b065a3a05a987e9e42866f9a01f8ab63fb62a65524fa56c3692fd6104a69e462b56570aae0c3278dd4212d5545a6eb77658ca25eb8fb5864c26aa41e340d3fd6dc8885a9ec838ce9e4b10e99d68d48ed7eddad744647a19db1fa10e27d94c0919ed0fbaccfe21cd04841011a0cd8c236d271bb151cba639c6cee34560a2d01e83e59c14b92b2b8326073910c52757daa23d757ebf833835638ab7f9957a983a6838e8506e2b0c7d94550daec5d3378fdc547cdb25cc86845f9b71170615482134a35a9b7179e88fe3eb424c0f1673b88ffd3920e00f3d0308d15e0368304e142060fe92a797476954c814a6f6089494e13ca17b1d911297196aac98f02b9d40f43928d0ef5a60063c8665e7d67e2e992b133fa8439fd06e99e842e8561d5a60f6d6deebc3df183a8bffda7803a5324f1b585fc531bebaacf7b67ff354904935500b5b6ba421406a637a3b5abf3bfc4d56a64b13c59c1f4abb81f26eef9c58957ec4c204ce76ae37150794045815e7b7e2e7647796a1d7992c68ec5f549126fe428d261c72f83dbaadfe5b7d6b94f2ba96e570642ae1f8ca7a4e4e6526a983930dc671b37def026955014b4a5f54ee66a3fc11d6117caba74f2a72d80325b839b0b15c72c8c83cda31a37ee99d12e8273eb4facca8c335cb36a486b6fc827bf4c0f75d0e584c69d5bc723ccbdc6c99411073724186adc687470424c39f700eaf30b250d19e73e94869f4c4e64f98a3899f782337a48e176b9907bfdf61b9eda22d2da7b0daf95fa052e865d5f3814b5e41011c7eedba7c990510aee3b48dbae58001cdb3d8162fd97ce0addf6e3cdc2a8f18a524ae0a5db29564e49626201bab575e2f800a1d14b15f9fc9e010ecf3a2cf960f39b0f95eebd98f2ca5685f6092a96ffa73eb6d4ea943a8f041f1034a2f53f7730498aa0a81883fb37cb26ce960718bcef34dddc8110680122bbf5170871f8e796ca8983fc12a5b4f903400c0c2dd9c79901a4dfbf54a503f2b1931927dca8c34c392d6a3d1887e3c06f2a97f3a18a66d0f8f7536502e4f0d13e31a4d30603468634ee44776b0ddbbad992fdde79be4e8a7d651d52cf602187522118fe6202ecceeef22e2b6f5487990852598eb2624a6a43e3e9f0bcfe5dcec0e2179c74c15fce3d6d39a3b62b3b7f72fdad1fd4f1a10cffd85372b734bdc937e9d503a7a4ee9ccc9e3dfcbf5088f803333a4a58fb0f8a6ed400ed7c82c8005176c2546f0e11992ba27d2ecc1263564352028104c37fc12a88ae01957a6d5cf40a46c213b22edf05d9d0e0d4ce1fb521b7fcdb45b2cbf6f088f21644ba7d0249e6b55145d6084b6189ab58f57db3dea2ff6afa789546b6c59ff39f8d2fcd29b7cc99c62e080148fed11740c51e9d928438891dbd96fa71a2136b7637f818dd9502dcd1282f957024c4a626dc515146007c05724de2b191b5348bff0597f3b14879bcc6f59327c231e6f72d02bd8232c403277b5e7a2000c160b6b5501d79f43764c0b114add67e0602fccf085260a2d4945993cf1abb465f44e9361ac9d558fa908d5c45ddca05479cb736d9b0cdc617dae2aec79a4fc0f2d2890e1b9dd390f48fd7970b62ace4cbeb76bd3a4f2d010d7ef56db7a29c127074c7444fa40cd25d212484f1c54b514165dc357bc6b556e6f2ebb9bd660e5f2416a7d6f6b4c25ab43ce4dca2baddb1df0fc06895f3f05577f59f0417fc89b44346ae3c98eea2ddf74fe8145b640799782b06e2b0c1cb4bc46789d8e3176bc4d596f615603c4f331494a8b3e5416daf7447e84da9d4217ac631dbade774ddd71e712544d015525e906c7f5630c2a0a7ed22d64dce29a39d6f570a9cabc6e1170dda1ccbf53a45771e220fbaaf13ad19f76d6a7efcfd5f7ff2fbc3960d440ae2009704911e7e28b4cbae5ec532079a8f0f087f4a157dbb704e00987fd77cd2e7b77b769cafd16fa7de40f41ad294e1e24371235714880308f2654fe1fdf7319feb4e678a7a3cf7f5bf3546d4d44788afa7067a7b4dff74b77443ad224a956ca775bb5ece9cbb244ba9313e5dd6d196521fc462514db30a3bfb8b9a5b08c06aa3da2b2dc1a45187ab69c0cc42ab51e5c5df83ffe716f38717425ed67a4fce46960c992a0de1559d86ddbe967505ff3e35809748dcd1581498ac8ad4ba3b45227ddfa150053799747be77f81f2bcf91ec5f7739cbfc51a3929dc60409d7b6dee0c64ece88307b8a80bbf0d2ad5d06135793765f6fea6e2c6add1fc94c13d9d58baa50e996eb997c33f6ec3cc5eab6fb6e54988d998a23b3bf3758acab1748e67e2cd0e08750a5c827363687e3868fc540f58314ca30b8a14f388b563282f88b1fb5939996eb5b8e628880343fb12af470fa0fd51a0345404a07976c73da96baa504db8f08a813d1e1dc39073c3ac3b3138b5d2f262b14e4f83f3e45e6693c055a76ca307ef9d08a3aa38706e68adb0b7b54d7af2d17c39f7f2e51dbe942dd5370990837d2edd498b080f75228c75cea450d352b24006973233e780656ac593f7267812a722d755f203c8e7e1d6ea3e8857e1d299759f1f556f3ee7b44fb784cf5fa81030789e49bdeb8882a5ba52cf022574f59961124a4f4954d3202a2c848e7fd5bcc99f11e402756a7e7ad8bb5be3d2bbe4d1377674af5e847a1b831011cacaddc54bf59d6e02a5e5ca52f58f6aa5318d766723fee3dda655b7f6bd145dcb4b35f93b8838eda63a3a2d6ee1b8c339bdf05eb627dfe0482a8f2c09ea40c9395dad09c861e3af1e7a62e3ce65b0aaea603f0e144b758c97af6ae21039e0fba4d70ac6d746b540d4c9a597f94a5de6e01ba19ecd799cbb509b6f21ebcff6e2647eed5126bec95e93350d133d92bd87c96a255e6d9a1654f0cb4cae4802dc76d5eabe7aff2cfc556cc05b0e9fed2a0c0db49bbf389ba67b090ef8a358e869f8c767b6375a0b2816e7dfc4a3540c866dd344c7d2f562ef82957182ade766ea3ecb714e58dd948975f2a7bbd17ff2a68f644819b4104e1c4dc63615ddf6f237897bfcee6b7ab1a16ac2abc42bd6e7627947a2d3f40f8aa514707cd5f17ef7ef6acb13eaed8fb54b2e96ab862deb47ee177d504c8305e05901288d251d1aa3e36415cfc3037af1103880145f20c4182acea1aa1467b9782fe5b36bf21480f62b3692842c83d84897f1eaf243c455d7279b15811d949407ab6d19c786d9fba11e9e614236c8c751aae0dd3c979861cdbe4e1dafb742bd2e1b5b254348ce9bd309500d4873331fc43f532e802cf08a1f186ce1b9f0d7b667db6a08ca90c8e2357f0569bd151ef0570c7b105d252002d4404bac7e0962a3d512d91264a7be2936452ab6a54e0d17837042086849bf4a570918fef8eb30d7234de166f476b857d980e290749ef01dc68f513bd9420c4faa4a4df605bd8dd27c7cd727bde95b26c276825e60fab014a530728db6b916bf1eebf2a60ae48d29a78f3bb19ca6e17030295c42922a94a143b280770f40b3a24c0b517d98bf359662fd850d0d36ec7b10b1a8481b761ffbeb1e4dcb078630858b0ffb2cdcff5374e932a2ee7f48962caf51754e243e1c3d0f788ad77c576344796fe247f3d010c2e0cf76a9cf6066570bceb5942fa46e3f2dbfccee22532acb9491b8564c57a573a528d45e76ce51786adb52a87e03e7c563b05c674eba04f5567cbfe35e358da23b4eb8ed8f09efc1d8e800a1d4b665dda21752d04f23c68ec64fbe5c59fcb2274160d35bc571e9b639b00fca63d9868ec06e9987dbbd8d5666ffd386b3ebf6fa97ce6fd791611017bcc647bc4475c05338e09a5e93a9ffc5cb2b891f16bae1069c22a717014174399d4933637e2b200aa9134db341dea575586af208ac7f1f03b2856e81ee0d09745d1f8b53a23f234cb6bcba98e48037dfa0bd226d0c3de2bc82ced85203fede09b7b2103a165257452a06dfac3906a075b2612a58b607d414b1f6f51ab4380bce532641a120f74168d45a9b02539071320385f55e34ebee599c1a6e5f87a9a131b313e551fb08d2e8249cc4bf4c7874a2cf2374afd7639b3244d1da86499b06a1e6630e80e454c03b03e20ed5ea7602e4534b7ca3a2769cea48aaee3f25b701330004eeacb54336936b41db7c726492e02b09b2f89027d1a972cc09847d58e5faae7f8a2c2b2ee05e7ed553104d8b8c0f128aecc0a29f93748b815e72bd079eecb9cb6891452529c025846bb9efc16edf9e39c5e6334b9202623e48418787ead269fb81acfd4fcc3b3a5101ed49c70f5fd6a9f3553643d91ae8815e8a791c49c32d2df30d7c687ef4964d029644dca4277c61654e1fd61ec4db4e3486fd2c1d2048903590d9c6096341b4cc6d1c992a4f18a7c0c72b589a41ed8b3bfcd0072be57fb96f8dc79bf0eaf3e695a1c8a80af1a60aace1744cd6182bd4de34eb804a9c4967c938462df2170f8df16ff0ae77ad3a7bd8d78d9080954fc29fae89fbfd3102535dbb4988cab7512a203d81bc4021eafb3d2c3198dbb7ec1da67665521bfcb48b33239b427098a7bdff0f04d2241a19535d64ced0f25a81b76146bae770e5aafdf12ac39afbcf8e4151068644db4959403282f1130cca7a1fcf755127c6cc80374d1a82b713663cc23350dd6db4652d34186755fdcc5e3e1a1360444a0afb732d0d61110eca1c0171a2d540dc838f0ff555bcd8cc0fc07f20edf053bb7175b290159f8c5124991bb29606ab591da0005812c57e5fd71f23e8a53731d5dd9985ad579dc3f3e3895ecf33c3c3fda2c868352e84891749d23b506ca73e20156b581ed282ce9aed033bd944d9d098b1b69089526c4c419e1b2baaa27e1190fbeeea5d69f8034ed2657a26c892019c0b0c09c019b3ee25b7516c74fc39bc12530cb5594ee024ebf2be1257e1210207af20ab09559b41c8925d532068174e9adeb79ac765b8a7372a94ee03f2ca8ead2cb303015394caf35568e8f2569682920b0d0618c3b9485dbd259cf2fc151dc8760f63201debf7e1318f9cb3dd2c126dab94ef00dba6e0544ac05688ac8a8075e0765e4c8d195bc98a5474ca2857a408b89095dfbc6fe9a44bf64aaac66803768b98ac7b0c4852d2eb8cfe19b1cdf7df7888ef739941daa09c1b9a4140e16eea3df02206fc43a6411f3438e4fddd8407115ea41450c689a0ed05064e65cb101771af3d8e7f423776b986af25a9f24b6b64fd6bfc50ec96471ce40d712455e975adf0637e6afde8799b1aea20de7b4c0142a0787a59f34a1fc20c2508ef57437c7da0b5cb82789bab0ae06e70c8c0decdfd1ad614f6227be01d281ca6c80cf5d72f1e462bbebe89e0d16c76196a55057fff0498745e5795eab8748af29e4518b8b5a2eb1d45df3b70722b7c300f630d5a78321d4838e3d00db2fd74e679fa0cebb6be421a9a0f3b32ab552f6f7c947e2b1aa9b27ab3a0117bf370852e466a3f1cb01173649010ac574c08a36a36a753aae116635d6e8f21a2fb99f98b098343e93ec6f887114d2f118d93325e38b9cc26f536a0792173019b07661a870691a64798a34f93d0e53605093d319a6e772627bd5fe352fc61c8d4c8313883195000a08e5b1de7171bf1fadecea19fa855fd1eb4fd5a26cf59ef762950c1c6f61a8df8b1f022e28a37ef0343be1e830c3a11969149d1a0ce2449271fc41761e280d7659131c5ea8ea21def12a6f6d067c5cb0951a9b8fb4f6e08dd2a8abc4a172ec6140a2fa36d85f326c934ae3fd54a2bfe7fbf3f94ef768cfdd92383e5b34b8a3b69c5d42f2972425f3788b5a5a816c549b7a318495ec8049fd16f797e8bf4f0e9ced9cb31c614aef48227cbc8fd3b93cead1db56f1791e88f53156be797a765bc78208fc71cfc2b828d583bc29f28fa8b9a39fdd53c6e18df9c76bf4dfedc653c32d41655db61561576cfdce4cc1e7842df1111921b97b40ff0da55f0da57b4124692e22069266ce97a0215a46fec254ba5b469e66ddf869621120ddd054fcd7d77f695fcc30ba0f58ac7b6d5d04a6a57f25415dd0e3fa16d503c6eeefcbbff137b9c1e0ab93e0810931fecac3274d0e3cdd914e8bf23b35109f79bbc7d1cadf6864a98124ccefd715ba7dd7f4135735ea17344a87e7bd6cf62a5ded1332562f940e45e4e6a13c723889e6756402df4fc366eedbbbc350a0598a64f61459780a7b066fcf8f4f3b572c55d49ba27463cae1e17e285dd75c00098b55638eafb5bfa3c7cf199b64732231575c0b44ffc34fa6aa50fdb3fc4dc76b88345c3664c280ef89547afe2981556d1ab64ac16eba280722a80dd6dfd9b0ab0d71bfcdf3b7d09c740b4af929fdeb17dff937ca0e3e9fb8e91e6fea8558eec2309df51096a7371684b0903348bab8e46337c1c74131f603fddd2a94ffdd84b40b259f6ffe9a31e54e12ec2d12a42857631195b088d32a9a041a0ff4386010d75861cf64817befc17c607a5cbd9ea5c57f918c14659954e8ff3bdf66327c3d73484c1b3add4190463ff40cf18e00e9163bd2703f39e2ced52e24cd7e2fec9493904fa785ce608e323c7c9ea648151c230a41eb9a47f6adac1c8518368492a3b7fb3e6184894591314e7af4375ed313b5e1d41bf889631394db50e66f6efbe7695bd79fefedde1ac0d26568f09ee8a48425d6f3eda237baa2b1aa322b549c7aa83ae9e34fb838432a32ce9078837585e9f626c9325d0066c2b64294af36c59283cd2273fead4629d6b423259a59f56ba00f3c317cef203717580ab6abdfebb310833dd2fbdc18ad6cdd691e34feab83e9231fb659ba77adf090e86a96748031a16597d00eed3fd2da965cc4d993773cb79d8d76be29cda78d653aa611acc8cefb168d0b9c6d60c483c089d8734117dd9c659af939b2885e6e88c61635a2504ae99d1251e810c5e83f45da3a53cb0124a1410f579451957f99245a78d6df0bf0ff5116bf7d33c04684a24a087fb9a37c83da918d97cc7870d91f90f5ee7b35352fbc9ce6f05ca6701afead380dc7a0cfd6e9fc8c29610bbd04e258d79f276ec971c07339ae30e8866f527fd2921349ac2da144896351c9993a7dc4e6c7005ee067f47942829738e3be762d10ba39758731a6cb03dae9371e57ed7967e468a18dba9487d5ea426676d822fedf8b1f7ce116add089f438b764672f422091170d99d0ca38b7ac7484df3ec16777a6475af9b6a0a024a573f4f60cbf3309efbe9aadcf169a72e5ed1091856e5916b0fb6b0c66c0afb1a40e3cc0085933cc8ddfc4c0ab21ccaf1101b14b33e3053f8affab48ad42c9388698f8e1794a4ea1a6da772146c2bf537b5c2f009f2fa1b5b83a091def9f06e3064da0352381042f01f305b720038aab3de806654622e62686ed3975e0718844e7975bb5003ad85ce57dd6ee8fa7d54f63be7ae99db505e3630fb993ba627cb689c8e1bdeb0fea5383713bf312182f91f445f60dad31fce16d3e61401fd4ef09fbdaccac23e405a2313d5ae8f2640049dbf6803452b88181b2e7256012c8dd01f600492bc8094cdab757e2c01bc16c742575890dc2ec623396703c3635c9e8fbfa865b0437f997559d2eebaa54c047a6b81ff81737a5ec8af9342725389ff6ccad7eccc16bf737feaf1e59b90489265090b79320beb7aebd04f4dbbbfd128ece63253711561a6a89f1f7147a00669243bab54e2863ef6cb63d60b5f42e6edd921c587201f0c66ad3d695a63552b089e2afb1e6c2ec565d637083e0b097da0e7087a6384fdf9aafe0e05a33164c061a6a6d86a7765ccac90cf744330b361f46aabd0c8157335d55b7eea61fdf844ec0fed06eb8cf99159350423449b8535ee1b6a0f9f634f4dd4e004b1f0726d8316f3e3f73ddf78659784673d9a100ff8f9f83f2b421e757a0df961f4dbf2fde54503649c1a12ed522e8ec0372163e33fce03d3d7f776dd18fa2bb8f429361682d72894cb14d9f8e825ea73e0abe90b10c9150144fc66b7705883503cab121fe932a427299693c36a4453fc95b319e617287ee968777dc5903d1f1a664d86ce186c31bdc553e181ec51f484ffc0894eed9f58535120a4748de465fdf2854c757b7ef14de9711e8510235ea7799c95f4673bd1967fc16b9bfbf30aeb3581399b7b23956a1d78411651445df6daa7f7e66af8cc85f7cf209da5be57e23476fa8efa9787f71292d770995541c54bc39beeb269f880e84ad55caa5785c1973e9ee7b93067b7e4fd80ca775a428d4e7a7d2e0ac7393dceecf252d61ba16bf04321c830ffa0b4d9a9a887936ccb3a84c4069ed74ca2ac353121cfaf43b8375e24bb7ba45f2b92f0bd6f68bdd4d1b6b25ebdd0630426430efb4cd4f35c9c8b0633b9f3243e039e403e342551256b61fdd9a4d0c9d1c9a78bbb3ccccd6e273ffa8e4160066a073846cd747c2b3f8c3d7e9087d2dfa36ae16cd0083eda02c3feec96a2ea75590967b3353e3af949dd96eb10ff8e2c61079f271f744545b07582bbd46518f8786b771e4de687edf64b28d619d7a92dc983b31415daebba71603a0f816bdcc5134a79552b70d48c68fc4aa328884e71cd2fd962e76a053c456189bd9df18713ec9e2541ad821cc5d3ec996d79278f2b578a9e7caf0205b2bd7d83d0e93a994481882700b836d2ccb7b6f5501f7569f8992b954358f4aec4b53b751535d5635bac70225d059a65e96a288dec4a420a0073ce02d17d4111dc9e775a36de23b5dd9370befb6d5cbe0cfac1374b171ff29860ecca0f9a50180851beff7df07538436a38574f6dd523f287f15608c31dc96983dead3e81e6e21732d489fac39ec93f433573e3d78c9956c66619abc77fb17363cb2724c1dc3cb98e0f2550e5a0ea35eb1fbad5cfae46246a093a5a2165f33830992cd049a82bcb7dbd63986df7211a4be00e3f6e575b20269ae8ba2a9dccb3494371a7b0eb8653a284e958b45b5165535c43c08e30ca1754f793abca878332080b2ba1fa4aecaa749568d9a623f01ed33fa3544322263aee779b587f59f71ebcc7d02f93eedaaf7c3718cb89095883a6fc6af3ac66697400250fd720449d1d4204913a890d1f74b97407dfe597011b0add80b0e7c3f17eb463eb22175cc32ca2b91fd8f0fbb04a80252083b882ab5124a85bbf9e8730550ff2fdfe86c852623ddc094b4faaa40d703bad320f5c5095f4f29f72e146aeb44d2ea9a52e7596d5b0bd01fb4551b4a1c961cfed3caac96a54cd67a8015c3c0c5c203cf9d0ab21b77b58183b5c8af7c7bc92f3ffba1d0d9596a81ca74f0e34f1c149a34e81d9498a35e2bc727a3e4dd314bbcee6e0bcb9200acf37090031b016a229b0c1ceadb40efed65dcd0e43875964ccabfc428bb4afe1228f52ed56df3f2f431b17b1cd4d344e5ecd3a4c93ca14305a07feeda940a3530f74624a51a59ce26b66146ef12345c8346a2051fbf7f38bd8b733a26202737d79597706664a9b1b32d7d63f44c64474178d95f8d65bdfa6c87a0bc99242d1444f0b2b78be2575a33070265b6030529537709d97c0afbff94d3523327f27a7a14b8ee5ed10df15c5f543d7ee2edb05f1495f9dc038ada22315f38ca02d4d6ead6f414472e31a6fded9072f1e95471df41a8bbee9424c91690db4fc3bc668016c66e3f0342e8161bf8985753840aca23fb9b10e6049932a0f545806ef99210025ccc94a4f2082b4d74afb385cc249fe5e00bcbc6ca5eec460021102a9bb8bf2fe45525fdf4e9857e54765277cb37b94b1983771f94009de6d05c8a87a15a82d70b2cbf2bebee676f74728b9c4ddcd802a4387a9ba609e08992e51d2b79dd3fa711854f23d4ee00ba2f314e6c5ebb6a38e712791fe6393030e54ce72c89a93ee209ddc1cf7e2c9434c8acaffde01cfc74107de1eb0f92b3403b5524af108bebac4a11453d857187b6e8b31a899d50dd96422555b0a6cfc064b6601c38a9f537a8c644c2c428f52fa4143f45c4110cd601dab2a8f16a8ba45f59a755564c22ff9275fa61e8d3e89b307cf428e77b0071544cfc271b6614388c014d592cbfc2bcdd8c665bc566d6d20ce750733ca836975bc2787c15b4907ffc179ebaea68d640022131ab98f3a7fc03636c996345aa28cef8ed6a9ca2dd6bcedfb1afb6da4d8a34d0ffcf37f2754f03580c79c41da35afa8398b8da1c16a4b017abbc4dab3804546d6ea3d5d0f1b9e3cc43b694a66b0c6cf1db9153173eba9821db2e06a73a0b761779a24a5e60f74b51f28a43a9fe53e88925d205bf116bceda8b6952fb2fc09be5fde04d71dee31a7b1f0062787355ed92f075bbaa0cd9709e49d3cdb2701bfca490dd054c654b53eef8eeb5cf8aca69b7dd465b5f0b870200cde0ccf7ce5bb9e0e5c7e99fccbcde550881f1ea96f9d8ca136a511f8636f8537f5bb66bf9bffa009283e090189496a67c8127ac705242a082a8e14a5b1efe59e6a0f8fc8a009e1feed7ef5a370f6c878bfea35bacf410e8d6c0eeafae93bd432d5f0825dfeb0470178bc58ee0f27a989b46349336302eb5b523c059fd8cab6ab22241640a3a0e2d661c92839c4fc0c23642c4a0eb40f59fd09a2107a8a077ab17422a7c9946626cce4af3ffc4e60ebd5c72f16b623b7364c5a8efea0db0476cec286c8f1462113d0796835814a6a146a624ac5dc27b3331d944b7125a2d72c21174b1eaaa2d374bf934c52b99ad64afcaa860970fd700b3656c0e7bc308a121728d0aa502108158e69012bddba9e61da6345919e58aaa65c79fce02fb2849c44441bff5493b053026de04b76f75da961ea30d623ef9cb72e16883ee7298c984ca6323c6a0b1d12123074741028258da0a03be5efffde51eb23b9f3d76048a65163443509e94bf00467d98ee02c97b89d7364290e2617faa2a24b15e19de026e04fd53e6ea1767bca8d39684bf40c7cbd3f7ac3f23ef7b0dcdedde8c6bd47ee6eefc4a7f035e4d9087404e08f0da3820a2d7fd64ba5fe56f3b8070231bd5c760e3975190c17f7c76892a0e487f5851c45bdf8f549b7a7d050592e2f6268dc747043fa405b5b8e9c54f2767ae67ea6f724210b2d4a81602ade77e22868bb0fc83c3299a23771b2bd3c73711c41bdd3123575abd090f37d8a695cea702df9c38f7b24a1c4bef72ae9230a022ac99d1fff792c9d340dba9a29fa4e190b9f182eb1c8728ba68045a4b24dbc8410b4bae348c01916b1d3475d8aa70717b2b5832a2c73142710b9230478d2447c506947545860f6c2ae05fb77e205bcb57b7dd5706ceed6f04dba6b58a12fc2e2bf7dff57cfa088782d74b54b6f078c3938440d3a6420cb9489e807e61e12303346bc55496a29d3ccb24d2a3b708b1c9dfbede9472d97ab9b7dc8e7732bfd6ef6c05b5d86b7b4efc88c7dfd7c10a5b2230f89442c46d68401e0d5f4cc7806a1099b6204db850d2e1413da5e30c3ad4819f4435dcf07903785d8bbbd66a4f54d7aeeffcdcc95e3f243442916b88a2161321d76ebcd8c941aef5a34ca2bff021e71fdefc23ed854bceb55ebcc44d03e2e5b25b3dbfd396041ecd511b2a7f37002aad2f34b7f32653d05b4bbb87a60855d276d76640e389e32be550bab2c3e36126b015866ac061c1e2f9e4a98555716829f8af71873fd47604d671afec0b383bc9f2fbb8cfbb9c3636e036394e94aac373d45fb42dddc7a6e0ea423b0acf1ad2d801e1b0367dd7a0cf24b4591516f6266387fd0940e4e4b5d4865c8615d72b776d7f5178db375145ee10f848d3622fe58777cdd74b731bf46e55c92350e142c8da696bec9e45b60edf0d32325d3de695561a26353b91b4206cd9219a0c027069d8ed5d26662bf654e07590dcf1b9d7f6f6d9c0983e8fec29a6aee54864ee4e7c644c32fae98c6fee42a2f7e319b49fe49ec6bbf59eba5158636769281135ac42c1024326be51b3de9abdf12471e1cc60d3261281db111d865dc50e84e902ac3bc37fbeb3905871a03aaecd3117b5eed6b3f333e4de2c8b4996556758d3b43adc606bf23c21b526cd804765e61930611b4243de9e293a59705b2e744f5449ea19ca209d6b354b078a38917e0550c6205035cd79c169daf804aaa10d5c1fec47c654e4f67e8f7e3679b5bdb5fb5b6889601b6ab48bfdaccbbc49d513c2b90aa16aede23c33c39dcd0afa9df42ccf916ed57e26daab074d9d4a3d738c7752fb3808caa465e57ee851d466e4a8767143d09ef7f87c009f7547df98794604e1366ef7443269ea13be81b496185a6f88dcbb64582c43db80e148e6e8fb7420d7ca20f6fa3c2f213547a3c7da7d601cdd8424db6aebca1e66b9a83e61beaf35017a9604f61d6130c22074c14ed3746d23def49aed4b4b821c4fdfefd47cf06a287835b6b344c57f0294eec471b080373884714407dd2ebc18ea68f86adf321be13e92a507cef3906d203237dc11c7e9b256461856a1635162e4705a311e028b35724177ae285b45b7a6f1cf5d617db5a673964120b911241047d4832304a2aa825e1539b25f35d99db20845ee3a332df9e5bf92afb13bb673065f8ab4ccf7f6483206fd9779593c8eadcb33e9a063ba974770a5e39b480e49d06b7e1da9a029f6b11d74c5045e3074e96a3d0b4d1b4b055a25eb21e951ed2baeaaba7fe311591bbd1f798542f3c7d51aeb9b402ae5278636c370f66e18ab3a7866af176168685a7585a657ac4e9449a29033ee43288d2acb563fa0588f9e743bd15e05f5289b10529f933aeb9ba5a5ef0f4ef61e3f6508578681797984c783bde577b6bf6217a0f12d63930f55c4a03a9daf2eae553d837205cb5985ee786ff4b7b578eb277ed0a49dcbce1d0b1b442272d4b905327cf79a895ccff2b2f1c7850f23aba19e1570476f3ab02f5aae018dc58f50dcbc0701535790664cafd59e9d202604bad2b60e020d09000f5c1a6fb968d28a7bda8201ae7bdf26dd947408f9b483ac9c46b32247a1baa9f5e673f34d504bdd58181321e86d07d0702eac5b587204ed4a37769ed44e959dcaffe10be1d867c36bec97b6743477fe2f26a716bd3e83586a086aeb8ac31306879a3b972e8dbc4ac5776db4fe22be887c397fe75b9eb213b5a4dd0dc5f2c6c966bfef5624fd911bb16d2e8cfcf068411a1bec722b96fdb878977843b0118a04a0e97f6a6dbb788257c059e22453e2026fb8858ac8b67614dea259a9e273794cd0b5675f2074a4b9bafdafffa3c1deec9eb742cfa49b255ddca099e86b59a40335ee1265d706741486e1d8b60a0983e3c7e150db130f3a658c6677507a6af0bdd69b1accac086c7c2676271381e0763b7171d5fb4b94002d4802a9a2a2d5e4c614c9cc11b415cbf62e030698a11004c6c87b2a2a3810d6d12f57fc1039ba916be78ac41f0e415ca80fe2d86c6f7ca181a326ac6363fce973273df524afc446b197fc919fb1ad6fe2fda137ce77eed390476ecabb20134ddbc8fec86504828aa4a688ad4c0b2ce50c593ff0178c20c03df3c72607ce6873f9e6add568dab745f8a85b141efe3aec900130f0cd2282fe13c310890998751833a982429531423ebf9c508d24c814d263d5841a3bc1b0787dd6cb83e5e1c80ee8626eaa7be84ee16a1a5010be9e15e3b1b61b075e95e0b84884973c2808443540cc0801f09d4f0fc68ed32bb6f58f69228de827ba6b82cfcafbcf5c7d0db4b2ea7c8b7a8a6309727efe1921b670d5e7a58f878dc70ab53a482e470741d0491be0c26d086321752ba813b70e276e2b479bb0e2c41a059363b11ff6dec3790183112d2cf8bd7ee3626be21027c19db0ef36637edd119072e33e2095cee1d9fa15ae10f3c1fc7a92a5a4574637a8ac951897aa168876d666bdd6cfedb4defb6da99a02ad4e8b0b1449c97e1898d46f0b3a5c689c927a47ed9848554cfe0524f680ff2d9721faaf02106b17afee18abbb9b3515c63a21a34e7f17b7e7ea1fd4b34458fe9916a76731452b87b584e5b5fbf92058b8cb77ad3e24c5bda15b7ec2e73babcec3ffd266aaae23f06e35ad94e9643b47a733480b00fc15ebf733c3432c75314b5941237fd671ac4cd6553b38e5f501529925cc27089b340a46907baea3ceb12b26e864520d86290f77ded29675420bcb7a1ff684aff36ce69c2a2d29e4f5015d3e8c0998caa5dd8b4cec54fcac33020142470e0ea267000ccb7dbfb3d157640b21739bda72dca8df19c941ade59e7ad294fed9095a84151fec6582aa0f8757500754212afe467fb9840fb4866cff9e3c2166766bc33026f3e5ddeef0b44091c6f2c8c968441fe5e1600bf8922451a671e414122899fff33204cb35a3f8fdf4a0db99f9ee54afb61220f226b36466623bcdbff495e0af69ac6379783589d514610c4397aba60ecb2719d86f9d83f519c12a87bbdd2cfc08761b2a7caf7aeca79b3e42b2429be575b19fab8d6a735394ac159d3e3c52678e2e4a11bbe83e021f352b045e297df032ab3551ef952879b0f1cc9da03fbc206ed248c70208a3986d4bbbfe125d1ffd5b736565642db666e77f9c60c218c01dd4a1fc7673ad5ee3def5e9b7d35676aeab7b78e77b76b1743de8893bfe52d1f787e4cf39d0c3a314b5823450838a1129b1d06445d3ad6f9f457178e7ea84f6c5df97d50a3fee760729a8b6a8fd12ccb5a9514eecefa7dc6b9317e97dd64efe1c7157866c7ec494de1f5d868f172431bcebc90e1ccdc237825376fa586dd20e57c07da87d17e8d11a6b5b322717f5e38d8e33ded42d40d099b38fe91b130143ca6926c4e85b3b7d14d8cff923df4c3ba5f801f6094314bd72538d5c635bd60be4ef09b7dba67c4be1e57992f743804490512cc52edbaf5759da45f190c2c4ecc051d6ffda5fdb2189fe218e9c06767d4e91ea6a543b9bf5d62a45850cd009d1d2bf70498c8e196dd113773e0bd793fbb9bb9ff6a0572c625f84d5f29ebf2d6220c716294bc8a1d350dd40912b90d31f898788e20f429c8064c17d90c80188d88271a8529967386fa791e22f2a0fbce00c5645cbf782fda86420a148f5566215f89ef5e0deda3819805863d02b55f0d908e587a6814ab58e1433c4aa3c1f935d181a65e4dee498a8c79377b84afae615dadd3d22637f4ddf2ceded37bde2cfcd374b1c22ad2da18905392dd047d9f689f4b3e64729cbf1e4141a9f45d948efd083a148d29be72dc1541c31f47105c7ee17f19271a1e946c751eeb974a620e9fea57b3be6fadc89e51940e7ccaa794f12eaafbc8e1f9b85bee8da8b8a5e10ef015836da9cfcb78670f8de3e5e26c6d418c600b350111a41a465777e8ecbd68f4632bf161493b884e463f8362af6b32bdd2fd96df92533ed13e1ac6434da13f0e0ad2881e407ae6574fdb0a6fa3412439b9338a153010467f4ffce5fa7f539c975dd325e25c4285d0e25f9bf2dd9e48e427da378cdd98d3f85da0b5aeb2a73cc14eeccc18d13044ea3c4eb1d0e9de5761d198ac154e09d8cc02178e9573ebc10ddc4263122f9e32fe37b212f92a2c442ae46fd5fee28d145c6ec1a13ca18274dac7eb2827a44d0996d94b5d0b3cd4868f630db327d3d24f3b9eff06585b05ed6221f4d72eb8cddc6d174d77c3abe9acf5c4904bfd92606b96602c3694c05e83e1222da8fc9e6ee08a0b3adb8dfde2c7dbebbd40a06dc4327d09df0a58466c488653ab0cd50e5844625a6944bd231c56f2b3ed005bac5e439bfaa6db3c34fb6ee98ad1c2311b07edbbfd3f614e7c727eba091fd06525439bfad3681646cfbe9b14e10fbf932517f6246b48d89d200b49cfb9ce8e0022e04e6ee3221e1883837b3e4a46f3af95202cf22c87cac12b64ee022400947429159e9d6a05d8fe3d56b120ef453041dc04c12f300d12849450842694a82f2ce1422ef88386c6e300d97c4346604555ed129c785b35a3f4700fba05f6044f413a68ae946d452d7a769b27a6f7d27053f7b311884e00b2c4d877ec7b5b6ea9472a71966400669aba4d49abeb3b5d351b32abb1735bbbc98eb784bb2e6418c1a299a38ec0351e036418832cff120e98f07eb7073c11f6f4f2b95c22ea341dba2e357c0714c631c5367ee6235343ff1a893fecce50ab6fa2240e61d22b73090b569c513443fd2a99c2747e0d2f9ce6ed3c7546dd1a2f3d4c235fde2bf3918c33ca42cce8c908a6b660388b0d9572b53a4a6ea76a55154373ff029a6c149c1c8f044f1a2cccf8123a15fa4b1b3ab0e3525b0cb4770e9c1a3f43196c865b821ac695c43f635323b58d3210095a1aee899fec41d1b478c908061213b047343af3bb993d30065011d4389939b870bdbc52b5527ecf4a81b9a401aa4cddf4762cbb9813556af2792e25ee644ec44079e7f64cacf6f171baa55d3c98ac3bd8b1b8433c10a0d4926d821753c3203d10fc5219dcdb1ae66ac77a8c370d4bbd322a2764caa7bd52170f56e3f7f488ca86f971f02c08357c8bc1a9a3c37dc09fb96f8a090102c9ec781cb56eb85b227410d66dfaec4ffab36143d95bcc0e54489c0aa837a893ba0b35c6d662a36f181c6ba00ce1371041f9bb0d3c2dd284e7c5303f905ad693d08705a777c8800893a00e3e7480b46635c75ef54c6c9ef6c1a523e5bf0ba74705f479fd083d621b2450e8838cd4d28123f34d4f2566336851158a6c4af7a4463b19f61615c59f917049b473a09f6d094cbf8d542020b794d269cb2a8c55eb3abf317f881efb8918d02c19d7cc7149bd107c999bbd35e6a059f9a74e0aeb7033ff76e22a928947d28fc4791ad76ff01d2acdea5d129e342b49800c1c1e969466c622a1651220d71eef3fb0725e2bbd4421fe00ef3d7a06f0cce9a2f588140800597b7ac30a15276746a308c378e798b350ffea9f9e310359457aeb1ce26f2e80df231ec746248a68a11ba355f63d292af3c795d85116f117cbe1fda5d2a00345552e3223e06a61f5d54b8ef306d94766709d05126d7074672d7153baa8e5587ede0d6b71753238bb170783de8b278a96bb1d766c312e269ee08e5d9b2dac344f10cde8fb038e56974ddf5c01c011829f8dc79e1b53df9a023790ef1758ee391d533fc21e65f5a8c04b143328a59e80fafab6566416d8f67106358976c8cff848925ebad39f38d308a9daa51edbe41dd4b6d809f5f8f7c0a9cd7c3cfe2f915b82c37cb38d4aea6479106913a70b7f4f9f72f8cce9151b7a520f84b7cf6bda840e31eea5614865c2edea960d8ffb6742f3dcc6820af3b0f49e35e20053f00c61ab1d6f088fb2c5a0fd9f090feffb9625e119a696108f22a34697169530bf4fcd51a11084cfb7f108472f2c8e6ab6296b5267daf7825c9214575518108a0aec7963f01942ec5f61601e14e7127519dc8473a2d8d0a58990a20ace6005bb50b36fd26a7adba45d883b8fa07ea2b47093e7d2e442025449302747e265b8411b7cabd88c4ac371a10e4ce8f7a8024214b93c875c16a7f9961b2019a19b701df155080eca235cf132ecc2af28a72618790a785fd6df0d9d2f67264cb65739a546e9148e982e5d89837f29ae05afa33d34b6d9420e977abc50b9163adbdee3939cfdae291452d404414947388432b2544c7f12627d56f626206b45128cacd54feed0955893840acc1918e800e0629154a748b7e69cc509f80bcf49bc8cbcfb03fa2cc8d8adc50f7ed32ef584ffb7c82ff0cb8d8d00dd37765669add421b1630ca34c57603e5a3aa733342e10bb06f1b338adc2280aab806a3c39c5e9e740e35149c199fe3cb2313f5f475b47d1660699775394301e984f276d50d85792c3eeee092ee95fac3bebb84dc9a42933ded9ef3f90cccddbe68083cfd205977d9c8a22b2984ce5fbad60d5e60b5c69fe375cbe1115a992aa04a34fd7c96189b6023678f763b3c1e1e6419bcdead483c8b390f05b8d06c87e1cc58e6426492d8ad1aa56d0e65613602d4458a4122e641bf22fa780f9b7a1ae5c2cd20d3eb7da0a34aae6dbf4676433dc1f82dae410238be1ee2c4105a185a2f2dd73abb2779de89b0730bbdc644a0a49fc427c38a60cfbc196932c58aeb7992db9dc5f2453534b99ca8f74c9d76fc7a5f6c5d6b972a53b98030386568d0d1f136ca76619735339eb8cd345948ffaba695120fa9709c2d83e29fdd798aa9a62fe0ffc6253b4e186ef3d7859ccc2df2100ba40d0b3421bbf68c7042163510b7353093dc755b96c4d89b92c53adac59f6a7fe93b7d94b77b5bf1ccf4402e6f4e681ce76db6432a31b7482bdd805c30224c0bcb9b2e0b120e34ccf6df3a650167501af5032fa83f90a0436b4c59a8f2b977332724a33e8c1c98b821386d7b8bb810baa3875968033b690dc5baa224d64724794be4b5a0658a0cab8b7d06923897812c12523163beda1632cc02559aa8accd152280839da8043a15b2d122d865633424e92a032378aab41f7101b31ffcfc6f30b523f2adcb5e2d799820591ac7aaa506637b82f17eb64b990a472dcee6d5fc9a58d2c9122be09d9917093f347d6b4c87de809fc59dab15e0c5b63ea101d16083de3811ef42a87afcd75ddd4804abcfb2f5d704a13230f864e7a873c9bd63711bb7e71074a82dfd5bc0368fdde78d72dc66345617f268f129f9440a983aa8ac9ed0a98a80b4e4119582d41c952f36975738db9bcc23e7d12957fac6fbbb723ce43697dbcb013b4a20d1978358eb56ef2de1dc33fb5fbfaf0f1f87c8e73b4a706d3a0b1c069c1c69d86bfc14552074ea1d30c9ae20f3a58e876b1108f481ee5d6a1910dcbcbe8cb6c4b50ee950108af971ad238b7022943a92e8551fa37f530061f55d99304a331c7adafbcc6e787d193e333932baddd7134d185702fe5ba18d3ec5fc8914b8c0a9224137e5a1996d8923a4ae0bee705a31ac158f0e670a1ac6b484e546fd8f1cc625351792199c6f8a36c06b15a2b552561713c7230093719e0be77c75a89747e38b68fe8e4f76cbbc9da5753c56a0c8187013ef756c9f5b0a2b13da5768e0e6d7410ee4a6bccaf646c5368b9622861c7bf409a38a1bfb9e9982e756752ccd1b17a7670d718f1d6ec90f32db4d012282af2c657c88b9c2cc5e959ccef483a5cab634155b8ec215d84c85490aec4b71de4fca915e317fb3b87e0fc3ec7a5298841a28f2a7dd0a441f0f2a2fb9f4f6b91f838497573bcef98fc49293fa7e591b84674a1bb3a931875adbe2c968938851f37f6e83107be8c44a20f3a5cb76f867a26fb35eb0dd6a9f5bd9ea964f8a14a92a637bd6c5c80dfab4581c5f2800b6eb333ab0d50822fe6eb0d7c2c870960ddefc7ae43cec74c7af1457f905a0f1e6fca2a0477c7a8711fb174279b4c4c6b6d6e75ea2c9eddd7e0c0967c5cb02d1414a950647a3a56940c61e0e8fb9f10517ed882d05bb2d40f5758304cd62785c1bacc60eeb8c1a552d80be0eb4de473c3ebbe2bc39c6728ce81d147cfd541170977875662949f4d52fb23656e7e117920e726531054926349b7e4f049537d85f75318f3cdf4c0426b7ed5fdb3276a86855b5b97de48c53a29aece41993147787fac69137355403fe9be512fde73104e6659d444ca640fa49751f8e3d57bdf7867e805134fdd35896d54ee3ea8bee5075645ee119ec32421347d9a784957204368bea54f3bdb1d434580822e7e9139c05e6bbf6488e74eaaa3eb909da1a4620fd8ca9279f697cf7865463885889f29bfe4876fd9497b5d78ed481526d72e3a4052eea05a7840df185abe2730f5eb5e3b5d8053753c1164285cc99f48473b6367121894fe86945633fc8f52fe15e786bff86606fd68ca23a069e9f61450346ebc3220f606ca6ca93b37b9a4dd573c84823440d02637bc4a9cd7d2afccc44d4fafc95e28238471fc9834503bee933d22c1d5e3a1e4df993d0fce697c3684aa2b00864ee2a27bfd089bda16c89d973df06a7d5634eaea82832349c2389d2018b158d03d5a4f155c7a8f3840d4809ae35be9b16a53aa74e7e1dd5be176c1590a28ce9c1fef03ccdd2440bf7132224e26d35012e4fbf471e74d9aa59f4aa11644978854d2414572138c4d5a4e02d8e1fef653f4496cd7b5b7ff8dc26f9ad6502f505ebb77d9d64f8e4e59dbb446636f16f9eaf911bed54878dbaa6ef7328b74308cc89c10ee936f4391ccabdaad8d637ac5415af058f7c6f2b1d23cf86d62452a04e1837dc2eb407a86cca6b601df206e654a99c9649e87804c0e2290e157f4a1a67c5d7b55c491b7010411647f6df4a6d8ae741aa93f7fdc43137ebdf03cda15019fb1813f9e2036f80d715d252e11aaa1423e13877eee5d6fe101d7b2648d0e9a70e2dda4c50d19c2a776ddb143b8175fce02f30722334586088a902a6f259c248aa9d3e491c782b88a2cf554f6baf4d111073fe922adf56fdaf57c34ec2d418e5ac4720cb3cdcc628a52974891addec3160c830d344112e63c2678141c18e8a06a49721443ac4e591457bbaa7f27fb72240f1900b836f24e2d2224820994216a0cba5401067a68a6cb8e49d06700964077492acd99ca80344435a51db9e88dc8b2f1ec2b93a4fc4d491f4bb93a9e92cb1db7ee57179ad0a2d767c337de3ea5e748c8c9454327785cbb0a7e7a3b45bf9b41662947e34fd97d7c5d84f7332742dcc12490f3c8125c4bb0cfd934e9f3443f625b6cf8d82d502cbb60eb1ed68239b441ba1b936c32e3d5e7798a26e509c75374a5b9005c3f6935d58e300455a2f1818ed9e58c3e410e5a6c3658608d0d9297f090d583d17097ee1207fa72577c5e166ad8247ba13e1096947310a919994b406cc225e2e82df088923b83179a1e137aaf3938e450d47eb7b69dadac376dc36283a761b04363066cce99c4dfcf81a7408edf9f5b8a8af004a24f1a3a8af52f7499afe6fd00fc1d67fd3ed5cfb62ef881850635411baa942b19191400d91449819f9a7fb4f749d5b87a039c39662829d4efa5410896a8bc9b70f3ba4779d9ea4c5524e2e6cae723c36338c51b68b12e1436a5c63f2ad82f5b5e7669efb09938eed0915ab14c4d6363de674aea4e516fc53c7a123a6fa779228e3894c129b6b4b2a8387104c852506709a193549c25ea093570ca032fa4a5d842b5f0badb39bf2780822e8c6899493355898dfd4bd55fcbc5c3ca3010744e71b024177d6417715c95b4a542ca440cff91f6dd1f9c0c6ad377bf15de86f0fa7e3d08a5319f0121c69f7556001f0d68072e1d5dcea75cabde8b1222dd94535e98e96d48a65aedfa25efb4e308ab99370af706d8ad939b3f61d1e3fe64ff006a304b09605b59c73f0d2edba4159eecab75ec1c27ac7d86d5d5ff974a94859fcbb070873f0139edf86a699118ec015b735aa786f615f1608a145019a67cf72a2b53766067a771f8ce536aa29637d9364e316907ca59e07381507bfa214c101da4cf85c1f38f023bcd8e93497f96130e172c7a5d5dc39b1be67f58b7eda99f45700887656ec43917cf616d40f0d01ab91bd520dfe9cf7f65fef860c09d643b731ef5fb366b4e843d193f29161cd66d12faa3f692ecf6a2f916c9e82c6d98a75c5c89405d1f1a9f81bbdd8a3db256e6e460ab4ee31104148303de7959bd91c63e95333bacd3a4307caf5b6c72f3e9317184f00aa6e122ababcb634d48d828339edbc232a33e0dfde1a47e0320296b60f2e1f7ca7d5f718470213c574a4a98d28a4746d8425e55ef1bb981196a0f6cc4e851f0336d48ef6a5c90551d1afa4e0c53227e6b5415452662eab3cb2b719b32331014e0be9b596f9a60c313635b99a293e2bfc587c17693dbec0d575fdb2ebae8dc7ab94a9298ae682f6d9c1ee26b43d87332c12d07625a35f6e8be0da001a0f033bf79a6f1d2073d502eeed6b576346c73caa16d98d6ee48826878280246ae5380d9cddbb4eba76c1089dabe3d4ccbf1ac8212996c7e5a93dc459b65a50eb588c11550ba51dbea8a7010052949ae717b3fee909fa9d7b8e01af2c93a3e36f26f407c23550db55e09df89110b75863ad40ab22d5a78ebbcdcd9a455edcc830d5711d809316823f217490c9755201847dd0d1da7be5afd6c66b8cb64ce106a2bb79c1dbc496086d95a039bea80766fb4c2132146821d909d717311459b586a5f678a1551f17c2f51897d3d5fc873ae35961394c94fec59cb7e2233add90ebf78fc380b971c1d15049a1ac59a31a860ac4b69c237ecdfc42e09442e258f01c90dd852a4ee532ecb61fc0a0048f0ebaa2d1d2f34a9afb564e25ec240d702d972ebb86825eb9192f381ba8c5e1b9f9cc68aafd67fc021e5f4b0a4b7944cb181866d12a5cc11336db5b7a78f485510b87d02106ca2c6c85007fb9de1edcd0476ccc54da9f11cd293f951c74ccb3f91448d418b0aa7b65537247b67f4a5c7c4889bfecc0eb0a50abd4ee12e72cd50f5490184d4cadcec6a25d20f0a1de2645796f0539bf2446a6fecf05f25071f05a3aebb059378877e34d027eb589fcfee48daa6fa8c78bc19e96dbc152df22d5a55a0224797608d37b72c02b78e7131117bb511e3eb3066d512276490241aa380825a7ad03bbaf908d7466602ad1fd34c758d4c39ec4a8b38732d099daf4b35651341dbe1294a492d91681682dce772bf48c2b25a2ac0a9231913b1d8ebbfa0d26b41c81058476cad07aa2b9af79de572df455c30522cc7f65850a9925216ae44c3bfefb6b65bcdb0796bcbe41b5f0e8ce64da38426415b401c5fbdeba08b0e89c77990022b3640cf70fd10aa5054a37a1b5c8be64a7e12825921c8c6f4ec17f03cacbf5dd6b613c02dbef0856556ea0aa3bc3169b0a26b5b60a965a98b23662c6fe05796c93a75f90c21ad8e5c8a4d77de9fa5ca50972d047891d91f51c17a97a06bb455efab0b110ffc976b99e65c36bb3cbfa559b6db9de57fd2a067bca9d0854fc20b57549b31710df074a8eb858e20991cc18b01686cd8fde47646d59b7eeccb45b4d30ecbda0b2bcd390c3a73438dae85fea022a554e48d79bf86ec4b526de19b3af371ccfa09b863611c3aa6a938436d205fa070605c4c98e77f994bf9f0077efcd432a026c5146c5e0345596742f55276a611642c1af164dbbd25d106f57de33d6015f08010447973ccbf8ebb0620d29db7e1e46315949f517009792f150efd76d5267414cc54f2e4c37498d8d843cba92474a6b23f043081570886a1d06e1018f0fc5de9a4f69478050151fe514882f06e37ee1f23c871c4d8badc41ec4ab4dbee536a9557394e8243a0095afd5d069aee6e308c4415d70f60b886fff2d098b09dce5a99569ca9689dd4fb076131d67a2272dee21606d9b1a73047e68df7586d3c324585bfd9b95dbf0f8d2b18e10e697ddbc902f6bbb974306b2aea7ce69752478df595d31f40adaa4b9de8410f0f59efed7eddc6f7dfe81ceaece991c132fb89e3a4db9267b2551015a6ebf5b8cc61774a3ffc63988acefeca646fbda24268642ad1c69f2c19315ab3d08714ef1b5a14fd44b1c4dfdfa9820d3117c617d89cd6e20da9ecefcdcef46a161b03478f172b5ada3628e25377bb15d6ab1292f4719138dccfa7febcc2539af7e55d6be558adb21d231def8583012ea7f5915f462107306c905e80cb3f64afee5b1514305b95eec0ed8d60c6220d9c6a38db06c5340f8c22aad9354abf5b4f5916eb632d984b640179b1b1cfa755fb7518646f5c1af95d4c4c3f97db4d782a7ceb2e67f94ad6c60b101faf69e41a87215683c40cf5ace5edac49f7282bd54324e61c7536a5721db65c7d9e1bc3aa3f8b56660ca2f1c2c22e1c4317da1e5ab7fb0eac7345f32bb7f260834fd888e6cb05e3eb738f6401f4722cc54f576033827554711292cde69cdd6a04fba5998d12b4d60a1262c1114bdd77011b96f994d618ba15d93fa015737a0a6b363de7fecbcc5133b7f3d8984058c3bd96b9468aa22ac573b831d49c7585881805e413d66d4ab1d655c65e5d762d236a693de6fa526c6a63d92f22106454b382f8ed2df8099bca0fa906d4430a801450375982360c9ba379049795fe2a626b4521c620b03e33cea85018dc700ee610eabe6e056afc2298d18e5f5340dff88fc0d0f65c8f55fa4ff8af635faf0c25e28df26c57c3f2c8ec04f18ecbf3ba5686a71cb34a93e6c4add10f3f9d11a308678a25adb5cc07428f19b8c2bf5e1cbf3ad56ba6500017a96e40bb8d43f197ff64247d19add35c1c372e180dd2051f89106f5335d6af23b53b1efe64464d5b531988926aa3400b16b83bdee4233d2fbbd9a9e736e1c232d0cbc854fcf8dbfcf518b2d0d5dd1c094229344b59b15447035dc98aaa862450cbbe8614633209f6e7a8ced50fac547eaef8a5e85598793aa91769fdba51ced80fc97a82e06a2fa083ee57e93c5b92238bfea4c3ed166309e0fb97d0db70be2c1066284fd6cc7fbe3ace3876eeb9c374235ba4eb3e6f00ce119feb0dc09084a6c70207850016257243650a82c471c1ea2a7be14f83f7a4238e7a2dc5034f7ec45b438ff142e5a0f0b5ba83962969dcdd151bb390ff88ff86534d484a4ea9c95841a50933a6c95ff44491ec6b7580af7c82bd77d37c930836846616c0e883d52e49a5d34a758afb8efbf7462e5523d74dcbbee83a6be7ebb3adc82d9ed26a43027360d298ba76138daa2450df45b0533b971857a14bac2d8b2e6391ab429c2562cd05e851b5bd6c07b95af6a66079b54705b31a07fda6139e02ee459d8b0cc56bd549c3ca06fb861124b2af2903b916c88cb9860f22bf66c53afe08bb246c387d6099b1beef69d8373be89115758c740cd794f87d8749d5be7783eb098de29b39c60e7e046fa2c11a1c2379822ae8efec6bb9ca02426ad5bf38168d04c9bd1035b8d424aa68f632d257ff8a997dfddb5f64ce26d530f00458ec5cb49f93b7a8e1686a6bf5e2c981a41e70c9d145407452e3d5399f0734e41fd5ea5ef4859ae233f84dd8f24cd9cd39919668c7bcd93c46b68c6dd841892616b977980e4dd0d38ea8a2d111c4c02cef0cabc0ee3485fa5a475e9ae49410c4ced5c90bfa71c046fbdbd9107b066729e78f3df9168e1b9c12c6f6c030fd47fb5f2f461cedc5d1d5fe48753c8ecd88b58e01f114018ce6007a55e0b3bb34e9f705c334de3c67e26a6c8d94953ca41d370a66d56e19e295afbc8811794c4c9dad0c193fa0d90233c6735f2c8b3bc92903e3e02ff8a7b4a85c8cb642ce26dd3264bb678a05885bcdb3b3fcfaff6f09d83cebe44987e28c83a79086238f12d0084e36fef23c799350503dc75ddf15fcb212dc07a646b4c6420b6d67e77f983a007edd9a61bf9c5f853255ff576870367da53a435ee404397cc2852d19c5bb7d49b029a87c9876b08a94499f4a0be7fd71795025a26eb5b073d703471080e38be3670ee5953a063ab861b53b872d365bd352dda6ca03bbeba39ed33346007978dd5a6b04e3a1560d59f1873097fb0f9ec2bd25c1fcc02f8de0e885693aad1aa0c3d6abbf9ea1f53720745cef69b4695ff347036c3c5daaee2bd1e123875e1812f194a1155c10625b6bb9ecb759eb1a053fece8ed640dbdbaeb388fe33dae828f73497519e46dc99b6da369dd65e163d0132b619dcc267c21bc458bc49ae10a4ccc37210560545be69c93b8c1b21234271bbdc57278bc5f7739cb485f2ac207105db16e5a3264d6145b1c6dc34228f4251e332eed77fe137a47d39a5786a4cfe73078b846b858fefe610177805ddbb5b822b739b9da19f4edb2a2f52f3c5a8386a39a193674332ceefcd5e343940aac7bb881010c6e9a3401664e102664d515b19e8126c41c3ab4d8d9bcbdb26aa187e470e8942c5782be17d07b77f2b8ab56f20a6c44df844fcc283cc1805f904dcb9e11976cd88be9b97d86379448f136f9a77734bfe066baa1231fd724b2c11321aeb7d55eb4e0e11d255897f89537f0e4655f0a7f1d081207a357c57d8e3f4808efecd8d93f79940fbe79f5ba9ac7b4883e73f8ff8a876d5149dcdf0fac7032ead45ea05decbe181ee0c84f8a646ff3803c46c23036009664bf37d7d6ef6965d58ee773e55865cfbf6f284a20d2e9a747f23389e3ca82b2143492d25ec864b54fdfdb91e7951a2b58a3cc0d1e7dbbb8d5454e4e765e9f50d47a14385ce9e7e4d7204e635e961d7d4947a4a9d9a7e956224399ff69533ef89c52707c625b7d2e928f3efb87691849a029b70ef635fcb26e5b40f30bca73ac797f367a6ef89d777f922ccc1627265049eb0abb1a4e3f316d6fb5d93f07fda5fe0eff5e9aa406041f8d6c59fe2ba7b017a6701a52e56ebfb727bb2ee251c02decff8b2cc54f1cbf8549742c29fc4537a3bd5858e3c2ba7fe4811b36ef07a1041c1e408d39f51456ecaeddbac513fe6ddd3b11879aac63fb1093d362437137a801e23e6bc95aa57775132ccd17a01d33faf8dc01c0bfe10ea2ddff8de0686add112e5c3878f2093c95510876424b78954b8e787b478eecce789c445867c108e304009cc8804581690d282212187e9805b1a0b388fb6f08f7baf9dbe00cb94395657e92fa61aaff9aba76653980a07ac22218413569aeca5c5c6995ca1cd6c24c96b1160e89c5d985c0ad8247dbf2dab5d588f34fc49626d7b50b08e71a766bfd46ae11b9a6cf5c7352e1f909313615f292dcd43fcc1534a4157e828d39109c95d570bd43309d4f3525808f9f7ea081538fdd112aded0bd2105c878316582f6c211d35c08b2a6e0d1b3b8b8a4d9504ffcc2cd99dad056647b0a22d2e1967c2d862f2bb25ee628daf8aabffaa3bb46e79fb28f07dc49552e081e9f91150c1fbd42283640b03eae598c20b04ede1a8894aea04429df04608153931a229a3f0a6b6d4e8904c8c5ef732e5d4f24cf9621cbd7659582b5c79c86ef4a0b2e0dc43f2d0d4249538140843d8c87f1bda0499242fc5dd7a5631022a8493e8dd06765e5c883884b5a69956abb90fc3e673c13916ccda814b3497cd6449920ad005b57d0b4801827511f95100e4f4b469f34da3ebe1fb2fe6b5bf34943a9b89d3c1ea81ec94c9c461c33da2149cb9c9a96d769779c5af01d062894cf1592c941948355a2ef63b564817aafc6672cb3a4b8ad8577c1eca8832e3afee0f99747b99fd756a13f31153b500ca260f17103c50202b130c200317fafbf296e2f66413a8e2bff2a042acffef218e0f65fb60759d4e039ed2d2d2589f3d2ae8206949d79673072a2a5335271edde5b75f70130d6b47f03c2a3bb7e0a90ddf0f3aa9b8cc75c70c67762860f2ba228d55cc448a56a24f0424bda2e90a6606b74e9715e2840027e1cda53612ae4e5471a636c114f3d9cecf29cf1a74ff653de0f18d76f7a4565508ba3220296fc73013fc07982ae7d9c43301f4ab6e0e8168764b19a1718f4346e2863b7091f07c0aeac1d833461732e9f8271ad6493ffb9ca20ba6f07986132e6cfb3a916a5eb8f06123b85fb080346b13c3d193b867748586cc763d290b339e47f7742952e48cbc9f8938050eee902124a0c3ef1f8587e384b95f3899839ac18553add9361f0835a47a8baa6525885fb494035eea0bded69b0d8517c6a90802a274fc94c162bba8d9d2c25c53785fc530a4ad2edda4be177bc2ffc9758b4d17d2671e337722d5ecaa1b9fb8d17c334a0a8cc549f5980235e4e0126d78847ca7540aec78960a96624efefaf684f96bcfed91b398b47f3fd5a9ab1aea0004745f64da76d5cb9e3082508dd69870d94dc511f0ba4b8fdcea95133ae20b0c8c73f1fdc9898bc3c0d358300e7dcb065196972213581cdab1050e2b6aeff966f752ad9d188440f47cfa662bbe6a41cd3f83fa6f3314ec7c9cbd6c338d301a08c1c88c36a0acc4ae51ffdca8a095bca6db527ab803ba4bddae0f399e605d5f14ea588598b1b3abcfdbbf2e73ad45bf6bf709ab1b123056a79da6caddcc17abf546a0ee72e030648b5a4cc23d97c547554536cfaa90126a4fb2de5451ec58c2bf2f6d64c56ac875064e494873e7369a282f53aeb8c6390dc741722beb45bc5ac0661ef22e8e0c2ee073f89ff4ee7f59ec92da80e424557668b4e127a59a6f48b2d0562049cd126d8a3a619352920d798666f7a20dfc59a733774a1258ea3607b7e1cc169c8964b520e0a5218c3098acf50d558f9e3ac226de9231dc15fdd01d7bf0984805406da89c05b65547e049ff602ff7d975a21a68b1629fb5369439b4bb3d94087385503f8fd0003c91c2c2870de1dacb51d18a86e60b04b556c17b214e16135c4aea345ba92046394510cff3fa699c413f8b7c0ac38481529c6f8fbc7df6f46990a2491950da74d8ff34708c973a81a75bd46a59571c932f6b6cb42820492c259754cd95aaf78ad10193d0a9c4e404d91b301eab082b823a59b80384fa05542a3febb8b803e2f61be626d4f8c75a95eca0a97aa58a913f72634f077c0cfb539f1b09a0b840d1ef44a11b684f2bb14d3db36e2d760175cbcbed70e103c0a61cf2ccebe138391cc4005d0091d1d3caa001f101267537fb4443e15b4cc22bcdc6bcc50a82659000859f77572bd56580f00bc356798f1398df04db6a8c28beddd80b6b927ab4b0078f2920e5bed85838aa14cdae54f201798355f799bd691331b12d72fc977912dc1e913707f1615adf706c0795706c1035dde26527e95ab2b614736680baeb2e653536a6c590674fbe77f401dbbd19eb2f6df38bcfb7f980c14736dfffae378ff7e6e2094f3c74d29920875f42a8054edaa02026952bcac4750c9f693f3240a63e7d5a36a47eccf5f87368e9155f01814ef8f8bf05467ebc47858eed50ffba35bc93c506cd7285738892a39be54b36dc9739d70547f6ed116ac484562099afbfeea4614a62aae47b69f8526586f0258f8c39720627a895edddbb4d9c60db166574669010456754833d5e130d3a7e9ac18ffbd6303f7f4ca420463b1fd17fab5423b36b804a4185e0b1b71b5038557d117113cc7971617e7bd515d65eba351616a02f2faf7d7e2b2e17a578cc52f60546f611d9490e83a7f935eba926622bfa792721e1d14fd5af4bb6f9a0abb67e6b34eff9ae0757756e7e4e186fa00d9a5ecadbcc1952c6848f155ad3f342bd1e4efdad1f068f157fe7cd81ba850f9a390bde96aaf9c73460ea8cbec1640e66f8ae605d093ad46aa628338940f3a95eebe586c71a52035939baf25a21fbf2773b0dca3f5a8dd1029e87e015bfecfc327e8a43a61952ce5637b9322244fd57254c2a5bcc326d48b09cd79eb5174f5bf2caa13d3c595edb9c1f03140a01a7f85f911671c1f42ce5d9bc19f3b1355eac6335957fc632e16690dc875f0b4b962b08f1a857d0a6c01234c2fb2629c4e131a0810736c5d280fe6e6d98f64b738d9e57c491b1743a124e2ca9aaae5331ec06b85c1a5875bb4e3d4764a74d43b27d4e180569d1f6ac104cc9136b21264dff5e90e849747b011d6c90586c5055d8c64f9b74e39541f78dc0c387dae5cad9f828a1c4b7d231edbf5ff58bb69ee57a7199373fa3e321fd76e76ed2617bcd469af77c1222569a216cc93c3f00da2ffa2f3cd62889550a21034a3ce242c75c6386f8ccd35fecaf9e18912d571ea81a6bd8b596bfa6c672910e563d08eb5ba658fbec234fda58f07ea9cbc2565438a1faf084edd378a160a11d56266da23d237ad0716e280936311af84e2adafc3bc5af0d68e4397f2639acd71777f5846eaebe0cbf487446af2ae1d875ca49603b1ab0c5fad50255b94f202b6f9b15adb230b9ec660a59272926937a562057753f49af1fd2939831194474a8999885f7fe71a4a22637639b32cb672b3f4646afb0a895fde09abc267fd7243c32fa28a9873ceb369755999b52b01c739803cc70a2506e4a51f28f309898b363cdc1147f67573e582b0e3e63b5c06dd24bd3774d1671e8d2767d737d5afb8853e0805a3d4ec52c76296b22ff6977c2d79cc5bf4bdb18339c6b6b547cc7557ae0592446e633500c6fcd3af5294c1d6979f8c347ef713246192aaa35dac38e3fa05defa25e640cf7e857b7e7f2010dbd03b2a858ad2ca16200901d383049e557cbd2be7b699df5f203fac53e04ebf61a21cfef2be2dbc6361a5e1498e4afa05e1b0b9364c33f3f322ec770d41a948747f2eb9794701e51a73d3e5a53cfb7170f1d39947f4c11ea7d8cb64e8583b6f4fbae9081ad74bbf4d48f2ceaa0c540b738975529757f4d1c9c6c7b4fa607d1f8abbb90e6d5f2b018b40be20ef22c8ee13de9b2778a5ba0d6e387b48bbc3fafa62f959bb41c693e7deef4f8d42c002013de945771c6adc581168272863961ce9755aa851a8d51d2c456bf03f196e3b244ec65e0c9929fd692484955ee3d005ccd259fcd65925339f895570e8557dafe583873c41adbf3c379b5aeba89bd18bc3099ada9dda5375524a2e2a936d794b0f8ad0a5b7da3e620e4cf5ca5bcfbc25f01601339bba577fb82cc1d3a6b306741f96af14294b41d7ad42a6595c70ec667643884f4903c3c994dd859fcfa2f680f6aa933325b57f85604fbedccbd3aa8a54857d7bc8e4df3aecdd474e6a8cd6db4976ee036294915e57a1bf1bb1104930c11560f8bbb6fb50a06902af3efd4a76331298f87b2dfa609b40353f66efa46a2105736b8ccf14e0aaeb5bec39c24e9915311c522ed702a7bd734bedf3e9f4304bd8856885c12cddf43957c3fac10680c3eca3a3ae002d002e16fa96f2a70d00ee809f2cce4eee2249afb7dd38f761dca50d5216839c8757abdf3f98bc9955fd3b6a791ecaa9a3513624c7f5cdf9e5d584e1a9431cf2d367825f500ebb3c791ba18c6ee2d3891f79cdd9e7373bb36ee5b6765929ac3fce92ce1cc149fbce50a9dad31345bb6e85e7bff6200247df645d289ef5a4438cf1d48d2940d7481948e7276b415541cde6fdd69f59fb044c38ad9632c3913a1df4df524da42aed796cfeea49f68817f5d1b854af772bd3e369e48b90a7487d0516084d62e161c8bb8bb233763f1200cd539331848208c4ed92f1ec7dec46c983eff5fb01ce41f5d6a0d5adc0532ba105f0ae0865aa58a09ce7790b0a7fe4005fb8a141cb900376fde29376a6feeb316ae36289973daed5db0c8f52d99144cf6354aab80a0314940f066be91175fa661ba6e3ccfc58a95021b082332e8db3496f32a706b81c88c1de00ae07c81d37142a0ad2b11c0a6dee6131a417d6ac50d752d9da550d1a93474f1e9cadadd24ae809c369190fe8fe44dd6fedd318e8208fd96f0031a5ffb72f704f22f342933b16fac45f6f774cde387463bb58635cb6a3f6f575e1626ef83e04290e1c51a60eb4801ea3cd2c1f29e5c1774e4f145f4f0c196ff93d4f726b60a55954f64437affc340e2ea6a2e3c090d550daf971e25b580f4973c6dff487221d7a3099c11f527f526532d2496b6a57b8ec5d3ee432d396474b2e0642fabc85af660a06909e6fff6ec5b6e92d1880f7438f4a4d85de272bf51e212f821dde91010d37ffca9bc6cd7a24778a99e3594d58caf1da6fead27e53b63b92387fa1564aa9110a877b8f435fbf1e6e5208ee6b4e7e30f4dcc1e4fcc5bbb8b805ce21de26e96b8f80e4aec853a0bf127305102651adb549234f0f4738b67176add868133fcf137f0bc1f9c5fe16b47bb00d37cdce6f59cfb991b5e2bdb10d483654fe3c4de0e25ebc2a680bcb1754ab2caebdfb0aa272342d6d3d5271c7e0feac722d0ac14c5e3946271deb0eb140059e0d338556844613d737ce3369fded9618fde1d3a293c703bb51e360ac3fd6505e9477cc2527e4f517f89950b42cdbb23f730cc151896b07b5faac3472101ed095de7a539c9ab8bd3f1ad3ec852e461a8bed4f404eead88a268417da5c572b448fe41e89d28a5a81d7145ef07fd46cce37f639a24a2b3a0f2ecfa646a784492f707a802c709bf3182ef27bc847c93c16aa5dfd349434a55da91ca2b0e8dc87d100746d62e2b81959723b37229f6c01543ad5b380af3337f8282142280fe45ad9e7b894936c9b7e021231c556ebad8f1af94b4c05db375956d3abc54890c64b8671b69ae0e5fa4d647d14b4a46de76be9656f49b7c2a9b4bbdf7041d315ea2874ab3042d5da421a4d4788fc51c60472a358f4f0781faff712a653508173f75cf53369e6f2a15d83ca8f236deeb6f34d73482e706c2946b17d2a4dd0fa717c88372d31ce61e0d9cc0cc1572955b8f37ae61ec9451bbdf85c3ef6b60b12974a0fba2931ad13804a99cac82d95e26b829879b8ccd51ad49aa1b573b055e3250a749cdc3e17bfcc08c194ac00c2a7b825e3fc785244c4a228cd2ceca97dbc4dbfe76f58b5f03f047a99c2777ab97101151c6fbfb1d01e2ab91ac6ab45d4d1fcabad1169c9da8c3a0a532fd94bfa1f507dfbdbebbc3e8443faa62ad4f5b52bb8e6223934c9f05fe79572e8a31414544d7d157c21b2ddace956e6701f716a589f3b83f5cc51af0d5ab40cbae007d1afdfe0adfd5734a756a0c663641ee212ffb0fa767b56421df313e86cea0aa3e28c59955e9a9480e2901a78459cecbc714c7025cea572adcfa615221d9ff106d9cc49b0c01515def5fc03108336b8c49902277031cb8ca19315c74d35bd6a67539f324d1d53e0edf805737197498ef228e203352e6866a37f7ba9cf57743f037750060956fc2c166c9c44f320ee2a63a38650fd236359f5740e45fe6ae3f52953ad2b08b51ffa858d984b303dde236bf0ab81f01845dead6c584e607729e385858dff3136dd2010d451e636f84fed05272de6704f8f8dc3d5d172cd45bee7d81ce3965180d6e80c0df4235ba5216ca0fbd73825a0a547c7854de181821466e52c699c9a121ce85fd49b026b9ba6b6519945cc7264c265e2e044bc33f4856cbb720696f00c2fba234124000c0870ded529f934f0eff385567651d0208eaeda2ed5ad865d01980540289c054e8fbd5149773a2b5a577988d6a541924c3b174488d528cecff7b87fccb01a8016ea358b933d8f37e5ed14193eba981e1c0f9273f3c571b7b18e75b943db3baac6a6f51a0560b38722f3abc7e06c70399e87ea0f442514b8f26cd12f0aa66d100e6251589fb283b106b485d26c4a2296d15c4a70140dc40eeb3e03bfbf192dd58252ea79f3162f1493ff13a5931454ec21354c628b8cd9cfd8b7ebe3dbbdac803688c0348019a7ba7a6715d06409f13afbe46b0910c3e7c7344f24cdd4379d7abbe29947c81c100415fb6eccf5384e40a9b1cb9eaa55a5a6a217e83c6ea57197212c88aedc33bf7c047af39739bc151b2108ab7d31bc375326f19b3059c1dd871f57d303b2ffef810b067d0816a7d359a7e98ad1c901457c63446825da47e58f5fe85c5eea6b1b9a10af8d30b46d1c9271d505ff3d914772a066228885571a7fdb3f4f0493c65953d9151091f410f1d470898a6f335cdaf7f33822631b257fd586f2558f55b4e3a693c01e53019671411cf5bd10c29db9ba2999c273deaede33efc83547df41bc5f2401be5c770ac16fbd3929d1847ad12e26fa2ae4e0fda4b67a53b9db1c7d1c112309784f904c33fe0064f0ad684c178e8d0958963700602657689e0070ed75349fa04cf2c7bfb6924ec19ceb90069c8d7a2eb1d9997c8e21ab7f1187e13d43ca9a2f12a983337b57bfef17ddecfa0f16efc936c7a0273dff56caabaac30509c043042c10114d7a0715398d3c002cafd2f1fff334b0a43ba88e2c6f9384bf74620ebc1d7d38c1a5440c2ce14fac6440217bbc9c83adea10fd0d648d5ee17dc0cdb4a5e99c653ce294273b09c9d6a7d2b244e8e43fa2125274eeeabb88f254ce97ceafdee06122da5b199267db045b9ce2f899d19a34232b0c77cb5ba841af03c4c0c28302bdd42e5a4693875c4f59f4ee9b89496c29475ea1a27632366f0da44250ec132a067f84b8fa724582efe15324ea4dad863cb9c1910c6f06aeeae27ed01068f5bedc66dc3f671200972e288d3f0d23529b540ea04998ecdc9db4d56c238a746d74a974a150fdab6ae618eaecd7a721f905e64aeb4cc3b38f3fa32e433b4f1f30fb1db8a07dff2f435cff38f71f45f3bdb94b8eb4a2035763e787da9906a547e344f563af324f54313708eda241ca1c939234aab60a240a9992b2946192c504a5c9c5fdc888cac3d200e27b0fd04cef840aad172006cf51491e4161da32194e744f8c40527f6f3ff80f84f74d7ba351a60666c5e6a0d1337d2fca93321df8966af7f859f18d8248aa00f4df11682aba738db03dcad07c55acaeb22fa04d4edeeb2eb014133a68c139497d2f4bb2397420fca30f8864512a5f498d8414e738b4e730f0fc80156643c8f2bd8ff9a4f213374ff3f198c0f873d08ec54b0c4dc8f84a08a4f8508e7c9cf1070eed699f8745becec1e55d051002bf227ffe9573d6388e1befee72b8d06eb4e35622c925e1804ec92708538ac6b4d9265ff326beeae8010c3085c81673edf9f0da7519a7a6b7e0648baab87034845b45598ec2c732b321d975e0ab2710d085aa9c28fd04529903d79c08dae537f27c3dfc9b7496fe9f9b10231e53746449083c70791f1471c3ce4f3b1e55a129ff8c784709018aca297d1e2f635aab56bd499abcc26180d87acc0d9a704c84c171699ef751be8cccc82d4863273f92de69e8b5d01e9e63405a8409c16d66b1cfa31bdd706c987aa4d88c728321c5c7eed2fbd27471df255778cb59f20aa9c71e3b5eacee5e0a74cf2c19a308cd6459bbbba68b3bc20e5cf30d3e3d8f35151c90866dad6e8c7af3f64e91a212ae65ef0083f36072dccc26c934f2015c64267e772ae481c1e823cfa6b82a85a02de580d597d0c6881abb67c36c963fefc440a892b615e8611b80480ce2dccef78b0abf3bb63fe92f1678eecbc4420afeb12adbfda41cb62f0952491cf325fa08e9601468b7b44107faa024b2ecf50691b8b796e019465bfc4102cd33ff2d12989dfe2ecdd8506fb01532c0362862c3998f1982699b758c81a18bb97fcec8420a8261dc5522e98a0d0561647c1df2a767f3226dab4910d3aad6043e801a4a62a89554e025cbda7df0eafc9fded51e115724047192429421778c284898deea801d63f364d2d14cb6cc5661b56b8a1b0a9a180fe93a6e713fef4a90f140a3ad6081238569965c5b6244364379cd38578c24bd389c7d52e79374211d564c118b74de1ac8ef6f40f237e699b5f7dc16cbf4692723e441579dba8cb754162e5f944e6d08197fc6d8dbde580b4513548f239e2c4140add32f6ebb9dd9b923a5c5f7d92c28865cfea381216e056c87937cd089e35390ec5a6e716d1c38f9489a42bdf30666ffa5c2897a78838224aa1534245325d2487e53f1e4142b8b66f5708f3576bb57a653fb5a35e615402c96d88040a5eabf499544363e2942f7fff3f5519e9f2d9031bf200efb64406c2460a3cdfb849b7bdd3cb111e92f9daa3f2dc2c6a62dde5c400cd90747043b2dffd109a6bb7ae211df6d69a52e3b26ae09db2fff8f996a7f394f79a19797ad3cababe8a4233f90a03345c3c70f9c0c5681e23919899468ab2c686d50c35a717c1e07945427b49eb059dc7153709f4d32af08da87124655d02250f84ff1739451f94e45c6746651e39d1b485016ff52d8668c6eebb507144f443276e1fb38a20153a9314949c32106da20ecea779269cd8376416aca2144ce14ef113e24254bd668f7da2ec321b125c5fb36470090424a49e3cd7ae953797ea7afd55b6852cec6b4d732f657662a8729b5f4598a83ee38c2d100622cec0a75740e6c39ff035ca42a7d39c9199663428ff4ff25476cd302cbeb05e42854844614d58458888dfada6c25c072fc33af6dd8e51f6addbdc1c81c4e07ac2fe5db6f4d33c94ffc7c2d2c7a62215af7bfba86e34df0d1a84098204e7cf739ad5243475eac12691493f5abc7ceacf48b1b927cd925de88074980b685082835b8ead6029ff77445674ae6464a802e93dd69e5abb703f0d630353df40b1e2e08242bb4ae0b329306b853a1431aa9960abda6430e014347223083222e41c829319887401e68ee4a46d92538bf1dca905a16edf08331d3ae54990f52c5c4f4dbe5f2952341283655ce1fc9f5568c9b99966c3bf93665e217fd395e14d9ca25ab795f1e863a909febd306523724466262723989f71311c2458aee1221618e8b27c85b1125372817094d3221b8c96c5940af6760c0e3328f9552faad156487d8399c84c46f8a11ca96beccfa1ff3017c6d72573d03ac9dd32cb3617d1ebbc2dc9036fc81795b8367abd76f8a6d8b7e673e9ee49829e8347ffa78d47be386766807aaabe1461fa64a131c6293c478cc00bdfd305dfded2c4c9efb961643d9085bee87d2dce5239c15da8b79a6feeffe10ce58b3e66d20dafc4954834137c522c6c433d505273941f834681131d33d84493b2f78ae947096b577c664fdba6fac73054a3f78731527d165a554103c9adcb4253b56d7cf2a191b6d43660a9b03379aef49b2cf31b5d620dc64c2b38eb242efe71d2f470886476fe8ec56b36848e8fc8eb07565fcfbab4b9e099729609e0617c9d12452213f39588c5cee02d33707dddcaea31041d0e7a54d51ed24d173b1dfdaf29994c28dde0deca7542ee94e973b4c6e9718e90049b4a647e0116a202232e736a0725971a5116d4b94fe293cd7d09832173d63bd34eb94b500d9b7994c28f4fe2c774bfa530f1d0896607bd71699de1130df0e516dd9c3960a8bf10f8a3a529d24f29ebe9ba83344a9ba97abb82bf3132b6ed48655e756904bcddff3974e7691e5dda55c51c81eeb56a0afd469263e6417a8661f1dcf9c8a395ca61617d4a88d7cd3d40deccb3d089053473216a115b5428025ba34686fccd91595da2db0d82bf6538209b471646982f6995065701011560a751a480b7327e8f6e847e9711ffead4d5ddfa3730f7e3fafcfb2ca58c3189fafedc39021fad1cc69a10af92a9733af8ccbc8763e6996db02104dbb7fe9a685659c91b5f3f54f640f24c8ca9bc9490c96070773ea0921ce3435f902500036f1b73300970dcc3a6022fb747acebb51ffb64e3f42fdd6394d347aa070a2017ff69ce007896ebcba5982087e08a22d912a4953451e81bd1c0fb07e4f9c58676236418bcc9463e7c2a99f238aee578eec73dd02405f5f31210c5839b24674d58b57d6b24b0b764b5137e71a6c1c92fd8f8c5d4e71980e06cfcb13e8fa0f9c174c48a8d38250ae8fcf6693d559505e87946edf49c2f38001fa03241cf26001c5a6c5c708663b62798c470cc8ee71a8aa03c25daf7dde1b6424d6bdf274b6883c7837c4bdc056c4c3fd98fe9daed5eda9b80095daa19026b8bb5b927b4a845e6418c6d0cba6a2741facaae92dc755190f0c4d60177f8ca1ee7309ff9668cd16874a18011393b02d67a2eee9d548f6ba30ffc95dc9d24f3f076ab49c17a17968a83f90a83f8126fb17c6ac5b7f37b80018c0f978c8e19c84f1057504b0409672f1100189e2556decd039bcfbdbfacb931b4180ab2a3b8950a99046cc1af3b49230f7a4095838a6743d03e85d0a8c26ed9d91149845b327627484e6e3649cdb52fa8a06446b71524c8a4adc6a4ca4d5b73ca1004b0634d11e53aa22b9e4d8a769960e30cca722166114b6ddbafb097117bb8ef2f1a8970ad5c99669ff5a3250ed0a7763418e0b5a3981d30dbe6cc8f44c740972a8c4f9ea8d596cc15b2e4f6e0ba3891e8c07d364216b554765abd9bcf2e57a78ce15809fdb94048ed2662a353234aab0e048cf171b2cf7a173cc5a7c783cbfe0351a6d2e97ee8a141eaba48ec3ca99db824cef73f9cd18e793260bf575d822c83cf7003dc43cd3e75db0c2bfd177027e107998e389aedd00ad51c238091713670c6e2ff2afb180ff0987632196f8dadb171eeb85c39b56cef9aa538f9043d7d4a5d666332d2d3fe525706b77e58aa7b03a3164c14589f89dcedc8575d0f577fe49531986ca00a2d2ad78129bce31c0a03b6169db603f5b0cbaaf4e26b6994384e83de39f69ada4d20a70d47229cda11688cdbf40009a8f42b882a0de75a0c700ad37d555b8d95e1e30f02850d4157b1248a1e910f2ae885d9c3e9fa6dd52dce7f7a44ada5cda6298d991b8d42f4f8206787abe8d6b4f181360e9cb324729f873dba2a08a67048fe99d58d6288d091ff0c2f5537ed1f4c92ea476ff236d038cd562e2ec55451397e76364434b177583532a7c6222d60fe7e23539ed458663a05ec1ca73de5d61f27ada6bc067f8e4c19d47636a8138f9beb0d1240e6e232b12929c296a5ebdb42cb678a8c68ea04a03b81c4494657a2e1330b7d3c2d76a804dc60b9fad3c75e0c12e9a422006dd388d1c08e383522c0c2c4a267c91acbb2ad8498e4c4280009505ae4d6cd85e063518dc152773666e1fc63238825de8f140b0fb62618e4f5c3daa8c41690502dda391867f0924d51d83b9f4df5e320e2863d5e4969350ded2e24d0904a91b8c18d73633b358932bbc212ad0d0ab2dbcc77db302b6f4833f0115030dc77fd279643b79a3ee706a7f9de5ed54723a42af0f2ff443407c20fef6c09b2e131c1243b50c37f7ad7c9ab0d97c0e68b93479f275e8a2cc1e57e54fbb89c7314817f8c05450a0cbd67a86603d98db54f7bf51846f3e5ba23a4110671ae50918c91da342107a5a4538b0438ac6b6fb5bd7d8332ca09c7858fbb71ee378873b938b1b052ec4e7685ced74836978aedfe564656626d061299101f29d55adda19cf0d97b6311923725e6b99038db9e908b0fa6dc098d949324cedc7010ea241feb247b198a3d2daa749aae322a7cd56899d8f273af6646b1bab794d80f5f72304a94a3668064cefc73b130013360d484f7f3416814337f77bbe460557d1e03051c429738861db2a11a86f17085a14f459ce1da21f3d9a8623efb1504546da1514d0fa18adb3883003413a17ad70afaad13496bbc3ad112773bc1a874754a04b8f9be112db46f163859614cd6f420a77142b1525c910e00ece9ad0cd17158c69363b9e16355b77e12653164049caa552974bff3a077125ddee637ea6e8d9c8427a1dee35fbf1225120b8db09890fb2648ed8570f75d106a552c4e381bf5b4e8eb156d05f8a11989248a5bccae2a3371caf3ba8f9db60822d948381d2ff258e026bd9e12006dcf44da15e29b8fab612f89e5b0498b18c3bcb8d60b1d2be38444feba96cb94c49c2cb6024a74f5ad30f9b7e5aa499c701f4d42f88407df803e08a2b4f53a38a7c95d3895f5ff1471f6cecfaea7cba90905e6e7fbd9c7badcba6120601ad17ae5fe6e978a8cd8e2948bc96a8981cfacad8c66ef28b0d60faa4dca86fe7d04323bb8092600bc2ab289596868fef9223adad7ab43340ac4b75a98322a3ffde81209e9ffe613f7a0592ae86a5d96df1f6279c91d1cb12002115ee240d9228e7c10a54b0597062ad69c49c93fee0ddac06daa486fbedd09c6e249b976efe234c8a5338a9102a63f95ff63be9edacbbd964113b98093921cc8c61ba1e6649af01fba0d876e97e74bf7dec0cc9442adbd5c416b71adf88f7fdc9f6e8244b3dc60c395dbfa1d345efaad708489bea616ef1cf2491e29fb304454b2c4ae7e97329b2944ef65cb116f309a4ca6add13dc8c92a309e0131cc929f525b60c314b321932c8d938f14bfd078536c15f58066bdf846f707bf830acc53d3cc78ef1220efdd198141d44c03a07605300ed3c995937ce6185767cde3c3d6315d5096b413b3717f6aeb1a59cae75542ee5aa2d70d01f7c9d27f1fb4d2bf35dfa8443afbfeda8526e021dc4e69becb70ebbebfbb37edee2ead430b46b644f6dbd257dff479c58da17cf9e61dedcbfcb4a4b0e1901c8edbea70daadb2eeed12bfa3881112048f610329efdef8806d6697240a90f1a2ab6cbbb9cfe95730f0628ba79902ded9485f47356639fe15de0eded5c73195c9078a1a7145f61094af9e684daee5b56a4c522c6fa3c8fa2b42993a410779c9285b1638c3d45febf34f951981cd193c4e73673598a0910f320c38842e7d2bc355193ab1a6f3639929a403070454139c1929725c2a0e89bfe153292551dea8aeb40bde36a781700f24b2676b4c80555ab02a6b0b31ab46de8e7cab5e0d51167328141befd33b76fb311e766b790e6bed976f59e6a79f2b1395e983b0680716080e73fb93ce141f36a4e31c77bfb3675072a9bd08ea24f6825f06cf2561495d2d5828aacfbb1e05201d8ce64f4597cdedb9e73e53deb386088ae12b5b58785a3a014a644bf516d3d252cbdcb1a7681b97c4476ef1becbaadce5985db7a4194831c559655db4ec570a2981320ee07380e0dc79f8115b26c044d7462c9ceff6a98a63d3ce57804c293e007806f3859f1960feea645a46c1a589fb264e08078d00343eb2b03c583f8f73a3a2d22c89db715f4992524670feb58c4f34b87c556327060edc0bde72c677415f5ae2ebb330ad1d2ad4c581b7f1950fbf2611e01eef51341d33fa13b973e6f8e8518168f00783d75fe5cb70262a7058edf5b9eae49d19ce4d7f0884629fb42036eb9c7d7f627ac1446a2d6dd265d0300e2fe3292d775ea4f78d950417fbdaddf8f6cc3b84c5a52ac57198ab9d5a98614717a1b4117d3d1b237a30770d682e353f691f80917165c371e2ebf855367774d55f0d70a6b94b204d772e2201de7e37e31a079a2b07db4ca3c5c10b8852373c5b13b53e9f77faf12dbf59a14b12a9c33cdbed3e193649f863d70844cc7f0e6389bf988b42c0901ec5863fdb1162a5bcebe58772b7cf5ec3b47d6fdf3d8a24dac8aee558ae3d38f6db16c1d4527a079307bede6d7f34bdcaa88e99febcfe409918bb2353ad519d3e759fb54339d251a84071900e14925d835a90eb67bb4eab98e8f8336523e1663b76eca941463a70b51b763d5f6de0d8552f89521ba70921b6f6d482a9cbd79415bd4e527083196fc71e3993a1ce1e7e803160e0a3394a8f8810206c88699cb6dca2a10835f4814461c1fc9492e3e2e5cbdcda99dd32b644d38abe33b5f7f3a4bc3822d111a8b0a9a2afb853bdab0b0e6019647fbb8d712a658c9da8e4c5f862f20fb94ade51cc55af75bba1630e9c1209a67e13330987266c1a190ff522375e3a13433d6cc75e748c5210a1b943428308b6d37cc5dce86fdf6876f518acd395e9830edc27905030f17f02889bfde3120ed53b91b56f72e18e0d3d580ea70e261bbaca1f2317ba10b0985f83f13260fe35117f3f4e00c34374a10d18e8a90bfa5e8b1753d4b9bae5a94b2fd8c4e92e08dd63e4913b6e48c371dcfe22b1ff677c468b5f43a2a3fdee82502ee006bb6eeffd2ea4e457c2c27ac2f4a73bb3ba885631164ae21b1dce60393d6c276016941aa1d0ed5524cfb63a1945fccf9e07cd09aebf0818c9e35b606b0c4b3546077f1d7ab8ee04284b52cb39eadcd0c16d35933e31382d24a3017b0da33c60e7d797947f1177661db8bf5450638e6140e2b52aa5882a3c3ea96789fe0cc595e94e8957d53ddcd501b5f3c4e84149959f61cdc9e90d6ee3745ef6a57d46ebbec1a04647f80e8ac6882a7531bf73e10c3a9eee3b7960da6264c89fd8df60cc81ac33406b8a98ba65decdcabb8d8c79729b7b995e4f510273e14a6a622e627adcefff7efdcf6ffe015596c5947ba888edc8725c4adbda1f505830bd1ccff93c0694c38af5158b0bbc6f96c7de0f1f33589fa3205c3a930a292771079b6dea3a1b06adc5ecc7bce9af37b909052d904a8189ce6fcefd048c69d621112c5a0f802eca345e5dcbaf0f54e6e96d9f630d80f9eb59bcd6cf7a5305a9b3531e70cefd50e576afbee35b39467202d78117fcb175599fba4325acc1c5afbd522c2dcbc176f5556e115f8491ae47778d9cbeea5b1bab79607cc318e4b6e9a726f46d138cb6e47f920d9680aeec433301677c8ce73ce025f1ce76b80aeccc8a6f3bc4d46e199044b06f9f3abd9c8fa79d64eb3690b09c2f0c840107564fb9c5a92888be00075ccca778753305e9b2913e575396bcf9262700967d36e99766b93b7bcfbd5719a299f3fd91382bfaa1a13aed5ca327e7bd40fd0c22dde1ba56b4c862debd55fe45ecc4f2d639fcb4cb980c33f2f2478f47607d8af9dd2633c0b1f566c6a339bb53a32844cc09d0e348205699f239dbf10c452e972b0dcdce6c9af77463d6bd5aaca7c8e5455e957f99e50f3ad1e9930ec6b19a3a77417f0e1f24c6cdad331a1d89ed64af1b8fd876bd18a1d14ed6a42f29a03a183d3a7230068ad9d08c7c1b194c8fa66d286bd368a3f5046862a63ed8a8a04e5735db9d56e9d2208fb709bc0456389aa0c5b60b55dca3e49e6c00e407bc105667fe62de191558cd73311d8cbbe8a0778768a498fc7e5f6fa70c38fc9599901dd4cc259aaac376a122e73eb475e7c7b1262bf255f2de4435c1a0bbd7c722823d9bd138125ee195c20bc8918722561a3f9f58585374eb1f3abceaa412f9cf80b160a98568175dafd479dd6046c3ca10a592372b3b3bb60b2fead0cfe5dee365f7f1718f18e7b5ce2e954ad48970671fa3a1e5ec8131ae6ea15099aa8751b92019678ca41b1eb39543d10b644fd4b6419281e23d24134d35157b6ea3bb6e4d088bce2ad8b962e028819acb171696418eeb4cf7b8ce2389175df362c9fbe6cda01e1df5285550ebdfc9e855245cd5f513e067bca4afc1d787b5c87e3727ab7a369dc578380a05b72a6fb24d4c75478a7d974e5a29abc38e03a3ced9fee3daa14ec8ffef4f2a6ecb4abb3ab5fab516caa33fc6890af492c485f3c0dbd231540c165a4695d89979363887c1f736569b11877fa83baafdf5369baea47ed1e9c319e44fe68f50c1ff952e1a5ac2f5cd59748083c3c4ce592d2165253901133ab813716e5e2443c555579889da7dbc62279f665c4d58f9fd92cf65cf9e2526601d766b7d1c192481bda24113a55b3fe8875b38a3a510dee0ad0b04e8768a002c391109128361cce73e8bad60b5aaa0419282dde303e5151764bf83e9e903e0017ee2632a471a28065af593599acf93892a885852001e30b8782d01a3aadca9abc6521181019b0979720ff56da654f3aa2fb560c1c236a9a7ab83c6198132e3b64b9a19615996218bbd24a7be1258ed5c9a4ef249e193a0aab3d2458697ab1872abe9911f4ba6214927aa090d5cab1b9c4267c0ef0fa0e57507b3e0afe5c578241ffd054748468b9bc4ae9f9e6e6cc7ca4e199a358f8821401c7a579b015ab9a52b4012f79813f4a5e3d14fec2c9f1133442882abfe4d34218ff5ede90245310d7c4828eebb44bf9c76d591c11cf8ae785542c0a31d98ef9e37c4b295d2f5b0b67e34e82ebb289558f368cc2006e74bf2ad462a9dc0b440bfba1e0d57e892b84e7cf4a3b3f4a9e97490adfa03c06d8e9a09bda7c372bb7ecaa1598abeab3ecad60748f91db1568da76b3d2addd625e643e9529e5082eb07c679bf15bd726b14b9fa3fcf94cced656fe8e8bcabc23e0cc6282acc724ce050d3800edba39b8584f29c82fe69378ddb177a0b8e5e81ed1576cb979917a28a2a142e3c4178b60ee9067488f668f10b749e165f6ce158ab750dfe8d5db06c64b044085d5cffbdeff1a051fe3ec2d4dc2eba22b9f50f4215fa365e4877934b7c2d6fa6c655c92b074e4bca43afa9da2cac17eaeafe891ff150daea6a6a8a7a9b86a4eaa3a263515eb8c148130f5c2fc44bc57f87b41ca173cb97305d0952d691bb838aec929410dda1e32849c67506911485928d7784c8304078e77c8f6d26fc33607cbb825b416eb7729e7b7fdf0b589d2898cb35a75b475cd6639dc90b2849e81b09a6e545c0d06f8c3ed10f9fdd4ac55d76abb66a0ae869cce9be2e7a614193ce69c25b347810950e6222712760e91302f4e3a1c50f9e5562fa6eb56212a7962a28007197f31c47516a89776561e511c7a6e06cf4d1bc79b10045f652e2c6d607ca6da92d6bce6552ca4af234b5c856f786eb5ecac1068988274674c331aea9ba69d77b9a23d812f02c2e43a163f937136de6841a926ffe3045d312783c77c529d6c99ae7a4624c643b6731b8dfa603efb7cd457677fbb16604b1dfd64ff990d2941662a47172c026a7a60844050ab1036de17a1c89a8781104352a8a1198903cd55470b891a60cb46de4052c4461d013c4a9d1c95235fafb3704e53958fa5e711c966ff80f0b0bddf9d5bf68f79a1e3eb04c7362f9a9c83c5a2daa80a68c7352cff0e97b2516b3301a8691d878bc428b2e61a183f5fb32f1d1dfeb1eec3c975a25b5280293839e3e0f4f876cde4fea34db5a355277d13ad0d90c93b81f71352eb55813f1c99fbc5ee3943cadced5b0712c1e444cb6b2b40ec62bfbd30dd2c623ba58830437a95d0784693bd0c0c43390b3fe62965ea72474ae998c896689b8165d7ae9aee6f2425994b738e11708efac6bcebcd1dc7f18f2cde2515c5ce20e077f8dc84e4f06439df8e64d716cf1da42b0dcd255f92329c688688291cce00eb3df5bf8d981e61bbf6e6e7223d24b7d261ee3c97b1862006d34e813c7c8d75e1092f746468a48a36ab73b54a948d558d45ec07824d29b80be2cf1a331000bb64d26b99e62216d179c647bb7422b19a8558047c4a0e47db58920a1166fb001d886c2d33d775ed6056a630bef176b8c8de1035440bb5f79a7f77d0f4ad717d4cb0fbfeff2f462c9606aa07398f8569c74cbbabd54a4cdc9d27319bb5ae5476013e3cdaf6ecc811545e373b8cc038ecea66bedd90fecb2098b50151346bdb43d72c22f87d9402287aba12ca19b69d4772b3708a3a804d2f97de686f2556e801886c01580a44783e7c5a2cf439ba3dc7f28d51ee06ddd677e3e0fde65f82d92b83fc816e03b045067e145fc072d363023aabda15b0d840307b66a487723a87adaa8c78a7b5bacab4892c3de8ad140d6144b62a93acdbf13e63ab51d7070e2d730c55457142a5c9a9480b5f0dff471a6d5b13b0d8d7191337953f9b2fad0918e3adbe101ea1dda1e6557c14536a667c352e0418dbbc4db1861419bcad089e676b2b275172280f78e865cf1cedb6c80fcb1d2e0af6ebb612a57d1e538b16d04bb2390a4461abae30a8be7ce4014dc62ffcee68aef729e50c7bd263d8492c972c9cc75bb73499f6717c1cdf6d7de0c99c56c92bc9a6d15704dd72eb6b68826de14cafc7e4fe33e80f2a5aee8d7260fcdd94b4187b2ebd250764335b22045916280c40953b27871c08a05ed89940360b470fb0b00238ecf56515f6c924c7278862cab199432cd7642d73b958385c053a4be9f59d39683be9126bcd2d7174884b09df0d344fff20c001677dc6651d0a5e4d6cd2911684f36bfd6f0ba9b0e40e6753699c63c803f1b94cd00c0a2b573799381a67026a9e38c28f4d9ce71c0a695437d12697bb3f2b78dcb7f052928fb37907a9797b84afd721f8f7f9485d96418184b3ab97bfd36cdec5b6d16d26aaf7cf132b5cad783a140b31dcf7875d93817e9bb748cf87be5ae1433555abf1331b68f5888e33d3443f90f246b9cbbae62789c3358c92ec2aa495d8ee90b924f9346a0568f6cdb2a8565ede567ec480c40f9aac6b8e7235ed2673baced9e4e8b50d79466299d944cb783b0f0b3aaa2fe9a88ecdb6cca40f69b1024772dfeb760743dd7168a8bef8eaaf0bfc85ecca9ed2368103638a8c8326c6e1a7143a5a493be3543522a1305860f5dbb4727f111bd09da029e6affcf5eb5af855d206e63363db86a26aea5f05fb5bbfad4775deb7f72de0915e96bebb3c38868ae06d9abc78ea22e730656f0f84527af9cfacb890789699404db0e6460e4e9254e083dbc5be010a369fcaa4de0958cd1ccd1065451008f54863bee02c48a9a9cc3ca4d667c14f1aba53d878039296f24812836f23851a45e5be543be6310338b5394e7e0cd1577e7f8d7e8cf8f94af13e19bdd24960886157acc4f3cbb8b73fbdc2100990a757fcaf7f336d6b91878844556e4b8e94c7eb32a980d098f38bd6d1eaefd86e79d409409801d3859dda803dd774a389534326be577b812e29cd0fa587d0483ec9c5459da5912bf4f8c4e1a567141eeca7f418d76371bb7e1911de168292e6b0f1cc03f79d608dc33873251f9d3348a614452b64b2347bfcb2a40a4ccadc0d08681b03cfcc0059dfda37eac0fb0c2ba7fd98d63293f38bece8f9d0550cac15a7fa69ecbeb00dd7c9161654fed22772740b9e1b62046ece79deae2975fcef8296dc14a06b3c1b68219461845f2b763577af35ee4aea840c01f6192614a77c72ff3b27e3025c75671770978d55155a9e3b3018ab8c792036d24320c73099391ff431d413247f7db69134b93fb5d48f634abea142cd8963c2dc6325557d21d80ccf1ae0a9cb6cdd8143e2c5c135306752fc36c0ac49cc03b3e7c5dfb4ea998eac12b161ab8eeb5760bd561ceeb01973a90eb376fff84ebb2e55e054263107a9a78f43fc650cf918bdca7bb9e7db253857e3878fdb20acca0462a7dab87708873528d19dd75073d271385879ca06fa2f20d50a7310b1df201e8f7288bb88e28f020ac7b55044d3105a9a889284585c8bf95fe836f50eb364ab3cd6f71118c1807404fadc64fb7a981017a7fca7f537db4d870ad8d4586d4216b67435f3533ea8976643efc7c136e9169a2c956cb3a65e494bd76612ff87464a6e98187612c5ac7a2765a9243c2874586c79dd1e25d018cac22c7b49a9c9270a098f0f4c09e5accebb0deef7c3f72d0a3c6e8e125dea0d506d3a749a9b7971a29a4634a3e724cda3d16f45d0f3ce8f4447c14748f6ed749896baa6bb0262cdad6bf8dc8ca6f4b68256c2e1ace746d2685b05cee205d635c1ae489073034105f0c5960c3461b29cfd636dbdda5b2ad5cecc3f6f757a284ad26dc0c3fc66bad698ad7e85b3bc14498bbef9fa974668aabd39cb18f4a44671ac3084f51fc70accaff3b167ff4190b038a554316b03435f3e031887cf6eba48acd409836cc6dd4bb27173c4ca718f74613592938a9213f7f5bf6f344f16f819a5c3f1069fb06beffdfb04a9326a23cde3fec4048e332d9e6dd31dc6cfe03653f0660035c5655c88d0169a971092e603673bb401724bcb6fbec089117ff4bbaf6caa3aa7b3d1751f62a19f626854b0099a94926d145301461250dd052b743469106527594fee8d26889b2b00c7d7cf369929bc1bec21de5babb7c6aeb5959b229805d789915106dfe3d7b1ffd0d691f3cecff8ea462b1bd26b7bbbb2919c35f0d6c0bcc56163eefad9b3865dad1cf7a999303f4af839f37e999f75b14c1239155ba1b10d210bbfdb0ff061de7718c2ef3b684eee2f4f22dec38d53c57e252679549f7b14abc4bb06c4e7cdfa956dc723fa7c21b27965452841a856b7bd2b3d43ec6831f378533da21bddf81b525e89363caefd79f05556f2116f3c293bfab31cbe108993842ac1dd8616d4d640164c509d368bcd2c008ce596123e06a2daf1013d8a34a8e1692d54b90266b40c2196621b135d97ebef645ef41f61e76b0077e4fe32e6196c3d1eb28714f5a1ea9d07d33f9424e42b8f1cde22e2b88404dd563e1c307754a429a81bf5d0e04d80099c79fbd8eac140de2d84313e717af6959c5be78dd9b7584e6b8749832420bc8588ff97893f47372fbd4b4d3d3befb35abda3fe8f4d52ca37bff532d95c617ef088d9c060e80b3f23c7fd1a795319834178598853bb3754485efbf0be0dd60c3a0dce1b10ebc50a4689f5a23b3d85c18e31a97d69c533c63befb3a8e987d0ed6559ab95bbf119814bbab61957b18df7e1b20b652ee2080a73b804f13ca0e4b5ffef555a37946594601fb37b65e9b4c9d783b752f017e7754fb8e4744ee3932410d8b90fdb3194560403d32bc9d12b5cf8d5d97c5d5d9c8e7c9cec3b7eca47ab9aed4f7f54f82a9897e6688ca85b15c4ecc68afc2bafb726c72822a7c291afe8d04541a7eafc44fd0bd1fde835969a8a3320c4f1487ab3cdb538968ca20ed175d55317000558fd79df844df5b33d28a14b0a98a17ddce50ab2eb4671dc8c533d9f4def3428f1360f530d77be4e1fd24960ec2021516b2fd88e75599aa413ec7129efe133e55c4748b37691bbe9cf7a51e8eaa785974d5673f12884a575b04b8e64ca3b67e3165d77550ce20b2f7122cd80dacb2c21598e6d2b3d2424a01ebf7f18031f038c7196a73985a8d065ae545554efc3f69bfee561fcf3416b73ef1e5546ca2a03cc02d318de35831530679d42d92a3177a376ffd17b38c295c6477d42fb84922d3d4b26c104abf4af4619a0ac7d341c0237063380a51e5d7c3eaa4324d143e188ad358201db629d25bddbecfdc5c9085e61bc416356100f81b295b45cc3640af9a8f4e84cad70dc9fd6e2efabb7aa4d874e498f6f97cb9d5ec846682a874719b6ca1b423d32c691628c61f797061084c2ecdc8ab927637093186f9036116f24c774865e60e20d791ee441e1a6f71da7c296e1e4b52d7cf0e656010fac6f9c9df29468f3d8bcaaf6aae8ef7b373075771b0637e21231577070232d412984ad9d06dd7758464c465d72fed3957dceddaf50263ad36ddc2572407323230141b29c011b6ab4223d90a859a2ca12ad74aceaf1eacbfae63551c902be04143c1e7193eb9d92983c5aeda5bcb34edee66905deb14f81bf08527f48203ba8164ed80a7462c1e8c539b50c27150cb9096efdc440e981cc24eedaac10f3c9f5e659d02bda54108d1ef4ee6be46582fdf95b303424494885a22997b00e2249cc5e91979d8d78a780a900f953fb59b4405cc6743efa8cb9d22c686efb185ea37fb03b30c37f6f04c58a2fc56cad3092107716ef5d7ada5b5cd7cae51ae93dba632b7b01df4b122b042c33cc30f50db7079ac75acd76bbaab13bc22cdd8a7ecafadd46d79b530afd1dd35775ca4fc6d798e70b6fa6d8fab3e1b352f3b7173a22c38a7ffd082cb4c82f2c354f508159d413e6e8915cd0fe09cc4914a46d522f624c278000c847f0eff7086a1bd22d3bf2d030a5a93f97bddd6d704d98a95d9d4d0ac8d9edb4acda7000a7df7ce8b81d11d8c45b684e12ed117863bdc246701389d92f4ba45550a9fcd43e14dd0a9d807d822725db44329016c8da639a833317f8c1328e6d10459a6efc58e57d44b8e31639e5d99698253928d2f9cf31b76dd52a572d41eced63414edaffe861631773cf57646cc21693286761f2289f8bd21511fc83f1a0a89e23e7f05888ea9dccb91a36502f5cd7ed9e6960ddda341105316ba85daa162a5faea89997e4faa726096518c85d1be26beff825a647855dcc9ab7e08de2facfa80bd21a7142a503409775ea073156f5bc8e95b772871fc7e489bd7733f4228226ec750090ba074d4a44524b36b1a6f7d1d45c4d47af7ede95d05793bf8af709c64c09551504b10ffc279190ac27ca10c65bd410d77614427c9ee1f4d123d3d1d1693d975f0451b5785ca0369a650621fbaa1917cb1858e7a0449aef2dfeb37b27e3f8a6caf195220a88de5306384ef58478e90622d3a9354629b1efeb3d0d1382e2823f9f356040005d33cde66b92a70a5082f4c9b23c90690e82e6d5dcdedac2769694851e9c4828b1d92c2ffc03332f1193ab5a66fd75dccacc09e30b94dc999b525e17e023da058576ad4582103a19de981b99f3e43776fb3d940738818f490f588b7ba10dab5a8f1c95e2d7a4fff68fbc1d20ea44d64321c81b14beee3fc459d66085365c045a0d96278ff4c5dfab2e7ade93b35f1b7bbfaf87bd49f9f5c3fc74a26a9288097870079524a4ec82273a4a8045a00803dd9556c9e9d0b101ab71732d5655c4b3251e0f988f7d33ddd3270d34114062600e6eb272ee740bd33f29582048063cdb5f84839f64321f40197e3e95972bb1b9be5151fc1cd531724fb23034de4f45fe7bc3f8b9be6d84dd083cbcc70859fd8a7b0fe13dc2542213ca7207f13ab40397d05397564f8b0ddda8d3cf2db7cf45fc608e5d47ffb53f27c57ea7897e48a62e6fd065582d1ecd7c36f435835b4f285e53c9e2554e53a909329c6288216e0174b8d88d4b09efbc49e93c3e5350a50e5dc9bb9da0a8d801c258a13b38c7464925a9645ed1845c5c827885f4991e77bec395b5b9696da26350e53aa0968de308e72c3990d4c536063be23f0fbad8869cb34aa242e36024521765e369f7c861f81d8d7dedccf42aa9c4f894b4b66e33a61f74b7e59e58d709aa15f1608b1c381d5275ebdc415c0bf071b3f431dc75ec2715d7e64f6d473a082ab15c13cafda9d6fbfddc11add180a03aad20dca82481f3e0d3a42393d2c0d6e3a5b50ec0a99337c2b6327adb8c99673aafe238e429adda409e435d2003b41dfb6ebc0c616ecec141d2081de5a6f435d79f269ea5ddff05fd883419d9b59dd961353a42a7ce7f21feebd07fbe95ffa2ba5f8b6793dbf551d5b78a820cf9add1ce813082287ec7a47b4d2648c7ba4339dbd40003b8a3b902b512fbec90c96831765def67ace28bb79c2c5344b799fcc9ff1e09f358b55b6bf6545e439ccfa179621559f4e12b48663b8e7e7166941148cd3959ca76cd6543cc62efdb66765d12cbc44dbea8d9e1bea4ea61662340a2142f25fa701cefc60167c43ad0b52295418a199fc040791a709fa35a28907e15292ad202a17cfa0cc806ec270b398847d42116150130956e7fda62bed6946d325ea1c377aae3366d92f3f2086d3261a4212a9118e2c0a69c45f6932ca9aba6c86cb2393907d5f35c605a2b77bd48030f6232965af2363aca016ee3a3751aaea53602adb4ea4ba1592a5b5d6a85eea4a35b817e9e3f34c0d99f3513a9fa21319f28627f627a9cf031204df9cedf5ed73136e0e2c5cf53ca6706aca1d3392f7cb39f88a060e79c2c463a87e37d2e8cddb842a30a54ef32980c2347b8f8132ecdcf84b18286835bfd2554907149291d0d563caa314e6bab9e6870e9ef06cf3e10ff8d49c5ee107b0f294565f7beb09429c84c0bae3839a3765c9ff64a910fd4a211be577775e1576a3d73e689259a40742ad6be4cb6f7faeef4bffba4cb6074f99b9723fd882c396bbe51b750374356e4a86037c6d470763dcc56b3bead58af1fe1d9fb79aa3f97cb4eda469fccf0b25c9ef571d984776210878471edbaedfccb6658b1c1ec18fb2e330f0ee0df6ea63d0b4aeb325ee3010b2fdad713ef91b254fb46fb8be7130099f3eb7dc5cb29d86952936b4e6f3e5ee2b99667841a2d8d1ebaf983e879e76afa7e218ff58bb3088ca9d39bf90fd369fa28e176655125bfa5d41e65d8086b813e542b4ca049999be14a1a697a4f2e0b1ad4453b148608892a23dd70e0ccbf40491d91228e40d391e16ca0cfa251c08c8d4d17b11d60995dd7b2f782f5e7b607db0c527614ae6296ab50c05000223519a2add4060e9deff52fc0239c2f21bb12b072b92f875cfacf44ab58ed81fc1852bf30bd556c67e373ded7f83f158b8b99e2bdae9e1dc84d203ac2a56c8d5775c57e3b4aa336cac1002413f1d73f71d145592948463b202c19240b0dd178075d07a987937a7d02f31812822cf93987f6da27b33faceaec45752fbf9c629aea85e21f13262d02ec9302a1addaa9dfaf007fa4ce576cb6c72e34861bb8fe9bdf4cc7dd1f6dd399295fb09da52482d15373eed7723ce33cf4c6662df8678f6af6aee73360f2aa03daf8967f5057aa679c3896834ff220ff8ef5518cb3839d563b246da83f5757ecdc6b098b7ed7e9e8f3445f49d5e447da8d57302ea60a41d13b78ac2f04f794d8659d883f4ebf2b84fb0240935b0fb3306bd0cb509fdb58c4ed33a6b2a479a1990fa728de108b1f707600dd0e18472c38e215b898070c6699652987ea2e014f0499e5441c9148f0a33c3998f0822c8ad307a8ac30ca311041a0ad43aa5933d84c13b651e215668cac48c984c09cd44a159cf88bffbe75065e36ed0c9caac68348c44f87bf0dd188c6077fcdff25af5012b59dfe9e9dc9780919583bda7addc286244798ca19d6962d674555d7d62c1d6fdd4d0672219d5a77a697b2fadd77520dc99e02bd336974a82b0e34659191b6ffa48b8361ce3672129a993cbbe3fbfc41506380250da09199a44269f5f29eb0cf49a9f55ec111ac6531a0bc0b61734d14739ecceaea32fe69224eca293f4f4a82740191d27ee1997b1e04a2d5036621e36438102d71eb75a3f6dd13439026a503171f79f35fac553fa02cc3caeebed292a2b0112f24c1cfda24a5f9a49a3378f123e93cd3f5ba276874d2ffb8ff7c3aa37f71b2bc8b92a9244e96152f0db29b9aa52f766e0b8e92eafc3ff10ac92db27fa437bcb9416d10ca1f93fddfdb685bb5553e99bcf97654606efdad0961f230aec4069086680c6bdce1f6b8f09f0e47dc04ea98947e5062cf39a717ed589fa61ffb9c71d5f095736f509890f077c37f0bdff7f3417eb138c3da52b0c25a07d56f5096924e7252c5a376cb7370e608cb4208e0a072cd8da6de36e4e8b32600f044b021053cec597f39b592ed4f41a600160f576d770cbf0666a678ec65d8fba723c44d6eed30ae163df8b6d81feb3b11c62d42348589a9c12604436e89c9981529071cae748b119efffe3c2bd0bb5c2f5c2709a548d488423c33ea38705ad595b5395191e8150e1deb59574e5259ba251d7fb6e794b97bc81f9a779d0bbd942aac5567cffc183324fc253511f4265889eecaede23f0527d8c3a6b07a51baed1ea1f03a24551ff4c76cd2154a04d05be24a65a35d2d147b315e893269e4687575e3bc2c48b27865357f9387c970cdc030d9098a3d219acacf2e254623400051aeed0502393bf3a5709b40e05e2de9cfd58fbfcf89ad22a605def8421aa14cff71770b25d9af7365e813549fdb1550a26ef0869d07bd0c42aa3a55ac482080fbcb729c7a569d8419042c76c6450da70281d24e4b8eee6164ad85c8d8b0af72a631a1f49f3b75f6e8c82fa1958b4456ed9e292b613966530b77653dc55411ef797df2661d28395153f1023fcd8778f315f64f30910e88f3db463412d2c5c76f3163f945df243d810017c480c69575fa95c7f5dd26de6242547e99ca65d994a23c7ea124799a740dd9bfb09c792c781f9de310382f15a7c0efd2e5e9ab9a818955b5cdc611a6a1f486d7e7dbb82e44d5dedccb4ca11e67d34d4297cdfed28f809f69484e96659f761703da1bb6e585c19ea04e57585603e3766109dd6e415d560403e39142b67ab1b062edfea9b8875e901cce6eb53cecfbea5d9efb91c7eeb45c10afa8e9c85486c5b7c07bbe3cd1a7993970ba6d4ebdccd28a9667dce820fa29c2d7ee761cf96adbafa8d0b2588975deb705f1bbd070c734f5bd5fda9003b9d3adceda9d9e50a4183be6bc30c7f8f1b96f65fa1c0ddcbc0a8ee344695fa99941a0efe0de214901e61a7149b7978df667407382400b5fd58fa845a16fab655f978efeaeb0c695f73ac8ccb05b3e195baff39b5ca67bf6addad028386df27b0f1b703c5ad602de90480af7663f509c10eb31d0b9a191443b9ef340106864ae0177d8bad6fd82045e48636a36a6e2d9d97d43d46f6286dfbbc823a55a5ccb880dc7592f26bd4646941750c02b200df5178f064525a2dda6a9a0fd70964f45b094a340cfede9282b66e9fcdb9ab3d41f363db835892654fe1febd5bec2501ddb51da09e828f28a2aa090a577b21a1e7caa93e74aee50a111f4844be162f52170f7d71b4cf036f76d0c387135e7c3f63987a1f765f8ef0f9a1801baf441e7da814ad2af2afbdfa44094b8351dafce4ea4bad29f71ae8f0da526463842105bec240e18df9ca7f1db4edd8275984a6f9f398042f72f15f3f3a90de4b3b5d2f74f2cc3a7386c21c2d27eee63183545423e778f2f19aaa9f427ebd6989bb4798555e343753fd49b2372634a264624cf6e16ef0879ef3c90e400b755117f53ca95674c199f0362da767b0eeaaf0d683ac7805a03887041797f7f73348807caccd784dcfd6acf0b444967c427a4f693aedf4ed9ab9ab592ba8a21a827c37c7892d0bd90663559a8a94c81992044799b596afbde8f9d7fae20afba10917fb9a8e9bef4a0cda87968ffcdfa8219d538d997d6d21783e5d7ba80c180ee83dae2766993e15bba1a13d7de880765534ec554230a7edb9348ec43d8a0ef6272b607264b0c94fe370b7f4fe23b7cfeba4ee88aee955d696e4933e6cc115bf22bfcf46c69612105267f0b733c157016ceb0e6eb16cee635286b94765adce59c03f5879d5034bc8bc800d30e35a286e21b4ea31ce385c5a9bedd743a969bb17541e399587295e653741029d1f04bc9be091611c752d6b3b601e4e60e6b926969c410c2c50f970c657f20e110e7d941b246fe69f798959c1858007449e3f6dbe19349fb10ec859b67ecf699b9cd6ad54958ce50f3526bd68cc56080d96b607d62b7f2294ae8e0ae6260b33e9a610fddbd7e9e284da5a68e2210171e037b94e19d6a4a6fcbd10986d492326f7d6609f5ba77af668c75fe8c2984999fcd8b4cba41ba7cea3ed1602c1a29092e5b24c63f9adc8d0bff05c8386a5a8455173bc683232d419497832c9d1886ba4f0847b07fb0bf8efc7aac75d6ad75c09da25794fa2d7d95f80ad0771544cd3fbf3655701e86c5fc0724602686311cd93969bba7c47bde98e484e016e035fe636d2d31623fa3dbae4737428d66cd7ba9f587f375ae907de4e02324dd0115bd65b3123ff1f416c20a86840e8ed402e59c72b3c03fa02e73669279136f69453168a212fed56bafe996eca9e20bbef37e884e259c4e343086c238c4d70d6a64c02b282c2e060238092e06b4b39733020facb1ef70187dbe0c9d939508ecc33434a64b17b87278331c97e14f7c6be5f3b616514a04be6c13ff9e33c6b30b59cebea5cf5ecd7a7269f7a9fb002e4be864b0fb0fba00696f8f791ba5b2eaf632d118be7c411c11b2eb79baaf056c2e242a4a2b99507b8d26827b8006233706e980cca1fbdf4d620e5883700b399ebbcae593ba1f24721d941147bbba1e5e7703254f8a5f7a54150ac6f822f970bcc7c3de5317f7683a2db132b0b1fe7ef07694a7cac0c8336fce3309cc817f23d48a0c721f6f3c7ea2f3db9d76ec225a22604b69ce6629333e8479681e3ca1575bb44a65844fb7e96c5c7f7357328d68f26ad407c1d3a57e189b942ecc64793d7cffdd21959839831a1fe60846ce99d9c2f0775d016554ce5807f72c33e8cfb18e543aa2c4da33d8742ee36d610b94aea3515985ea2bd3a09c663705c0338680d8b0141e16f5bc3846630e458892df7d272dd59e8c34247a00e89ea5aebcc9825be54a0251c4cba8c10d4aaef7f7ffb1cb06061fdb362314110ca767d6ae80fdc70caee9a8dd02dee79fa46a61467e4eabdf1e399a20e425848d14a5a318fdd0449fd39141ab2070e8829597baa01a191c00b4e00d47f0c46aff174af25525dd7fb02ac6f029b6f0db55ba167b40b7d68350f4f5c82ebbe94a65512d76d23e8a4dbb7cace7f31b062f30d19ba02b77047e21d014fc5efb3283dc4a9906bfb073af6951814599db8fe2646a73cc367bf50a2589f5bf46df1def7559ffc1351c25ce7d394fd2bfb0aeacd1aec63081f10ca8a0dc82892b78a831899acc3bbadab23d322a72e8fdd161ab66db3948eca3b45cce0dfd375159a35d7449c61359cef49103f6aa0cd39bd5a3c2b6f8a92a09986e938b7cc22c14002cd261777091bef930f673b56c0e2724f087a1d7ff9cada8d85d46d0314398b1bd3020a3125783d96059903624c92083ccdf7327783c7d2267e91c25c5b0f875b62f8ce4094d7e7ffbcce86185f8e695db4438c8237fb50a2429405fb51e76e9bc3375bc3fa71ccc67a7d6fae3625eae2340fcd69550f75ef4c519d079fb1186f726ea381b006ab53d49b2778d87d3e3790acae3a8836fb782f60c272fa8a5bde3d1756804b8dd39ee8b4445932537163b80aefb3c8e9c014c1696530d61e39678c89902c5964db37670c2db0454110159772f5262cd79c737181bc8b0f5395f2f79c2a1b9040acd5ed5a2095edb6ee93f66907f127a66501b23d12f391d48b7d9824e51c4c8b67407fa94fdf059022dda00069cb203b422dcd49ac57a51644b7763f806087279dfc449611a632417096f60a2fb59e86a8aae850c62abf4eb904c396dc22c420f68817121a9053eb10479848933c75bace714c310097a70ed7fc1f56a8905ebbd8cd618f6109b309be09bccf1c630a3aec62d968d2d522e70259979fa04f5052a274c745daf2992865b8f04a4c54601c0c7ce4ebd1b1550d8e3ba73577025a69bc4f02283889d877101e8229f74c19f8d3b6418940bfc357ecf38a91e953cd5f95521ad5582a7f52df2ed04b910d169c4f2bba864ef0f6c158047be3da940bee13ef5369d3dde81e79798f80b7fcdfc3e313d5b5397c7983ea7ac8d6d59ff9b1ea9003435f39894b6a7f5ade481c855533b4c3788c020773b784029e4abd83dfb9769acb309f4d7c324e649e91111c77d3b609733fe0b78e57bdd7c372e2fe8f445aa4e337ae0fbf4d93ab07d0f655e0ec5eab31c83bd24e933ad29a559b61cc17b9cc943ad34a8a9674161ad62c494fce9209fe4c1e15d867507fcd32eec807b170a4eedc312471897e2e463a816fd54da2d4d3f8431a7873a1beeeaa3b458b3fab46af628a805f32185468e4f0e903bcc64eb571f5d30f80652418a49e440c1fb4724e79dffe7258814690c2952f972a88c3758915742bdab8809f60f06212b2ac6393d5eead1e4a0a13d28f4294e0127d1d398e7e851f47420363b4e07ff57f6ccaeeb6999cec1dc88cd4316138672616237978a3381c83220b59b4c50532129ff4bdd361fd24e16a2004d3bcb5fdf3a8bb882a4ed6d85d5cbd800b09cc4c2c9925a4692e24abb1f2905b5052cbdf6c7c97671938bbd6625bb00109c5183b6262e59aec0c127e33066777c75d2bda1a2974ccc901a19ee8caea36870e8a4b9ebae0234123ff44409d08483247ac2ba76c6a3d57142488535dfa100df5c1581e468efd47b9db9d6a77a5fce01f892b60e1a39f77e4cb254c60eb2addc26ccb97ad9b8b1564a52a43d4a81cc603c557e5c6792dfdbcad686818c198b56cca834fb53b081bf7b8f1b56e010dbfd345458110724e9981a614c9030dcf9df6ed03742514ad375082d40b3274b36609a810cee4ce8f6bf3763469264c703ad583f7fe89b14ae0032eae87b5413e83ff70ac516645a59c8b8fee8b6d1a8c3e80d4df979ad8506a66a282b325f4d8fe9ba50a6c88aee1d5b1ea26975a0199b19d18059ff117466b1b33878fa7ef9126fd494b68a11f4effba11d9295f757790c8abe2d8318b383f241956113c36b0e6dbc2db0897322b65b683fa3158e26afdd30f9149454dfbfa3591fba6fbe6f42a2e6a4db658f24b6aa17dda15c3a603b9409a66c0193a5297fa36ddd354e8a8cb38215ed76e9d7dfa70244c12b83ee48bad8ed86888864bc57fdb7d3f8a2e9730bc9be9ff563fbce9c71f06191bd88a736f7cc7b6b772708f6eb3fd565b0ec696937f5e85ae5f578108b7a19fb0eaaf5644cff2b622bb0fcb1de2698c20eb299ffff2baafb81480de9710e4e99f9e9f34b49e72f600908a24dcec86e543fbcd972d0855071fe6063a0f9bf85cf8e013eb0e550fb3e2c51d78972552b10f936df618c0f38e7123c541d59c5b3c6f49444117780022bc57bba699c98cf989a6ac7d7f2b0c18e7618742f012f858656785b583fde48ec8eb06706f35fd16322c8de42563985c216006b84bd92bef9e863a79134f7fdec69f3b47c182a7cbbe76a71f473557c5edb319a56851b92b6de1479302ea28dd75411b5126a1354f612b4144a75af4c38b72d3d7ce9a9c7fa48106427306b1b39c4a8205a688a8ae273a2dc616da53035e8cde8dceca774b19a86ed9251b741dabd3ce1f2fc8cc09607c0793abee21d499f6a3a860d16b899bcde6d212d615ae42acca6e1c40811c310d5e415487968af4f6418fefb44cdd8c4f76baf4875574a6c6ca080cfe0dfcfe7de9fce940ebbfcda2954bc13f8132e60abc98f652940be0f309e7f756b09a419dd20135ff974dbbcc63e7272dd41a5ff25a400103de35c51432bc2508aff0486cfd485078e4c56f2d7b105fd5d69a179040d3c5736a3f2b27fe6aafaf534d9718d80a716012d9455b070d0dffb94d4ceef81484808e7b58cd03fa43c6a23e996bcde17701faa3404c540b5800470d616e86116695c5693ce2e2831216defb6aa45a614704411535e585c07770795b84ebeb17367be6a36ed847d20358a5e884ac015bb4bbecf47a70cae35953a3c061cf1501d9643ed045dd9137d7b252941af391b928262773da3f90473746c95839303ed251f6913b3ca7416cdae28dba7e87daecf2986b0dc345fdd34c013708da5b8bdba1ceb783d608c7a310d023a8a8cdd7d049fd3aafdd12396c1e186bbb3350ede0e858e279b3d5cb7ce1ba76d3d2bfd0bfab915ba4e08bb9cff84b65604f2f1258f5b8cbe56ad85a79c92e5e91822566e3bd8dc1fc774b784c6ea643c268a1fdb7139390dd125ff9d47cacfb008f78c9fea7cc08a2ff8238d7997535ef23d362fa0bcea826d82de6c690e75f1d58bee022557f724732190dd1fa5d653ab1886d6f38b818ccc48a737ff20fe51680b41c22f5776662e5309a72ef277f08f5691f1befb4fd0797ef3c58122c815f7a4234ebe4bf4d90ec2210e1d6499761afdd6199bc6d40c47deb2aa5300a59521ef790337fb16282cbca1480445dc39ed23ac08df5a6521353ed41094a09812a1015a875d6957be017c12d81db4d070c0dc539b3e0c86a7656f1ca415e971c8afa2c8668f92e9bd98d0c889acbc245d5f4dceed3074d245bc39dc5918e204dec73a9b8ed4109c0d6904a037492eb1dd25f51eea426149c38cef23c175871d4c52837c22d32fb355ccad696654c87ada90a3a0dbd7b438535b52f511e362b128b7c2cb08e508109544f41d983f41ec021fd62092532f8e73b8e82ba3adbcd736928c9ae1d231cd58000ee440715aa331c72e5499e8fbe092a19d21730543a9d8c2ee9df127a1e68a95ed09909ed1da5e2cd66dec5505051ac9a880909d1bdec412f1f26dc2d54c9b3bb7a268beddc889220745bd2114512bd3c96688714ce5f4ff2bee63b4ea2a6884add7d26f2d41aeb4bc0e96e0c5c3dabf6c61765fff407cc19e768f1d9db29d9400bae1f80bd79e6a6d221eca51c8670d5af2d422f976b315f832d73e60529ae78311ab525a4fcb499d0afe7d28ce537945d60d82e2a6760b0f21f8516737bf773956ae002857f8f9364c7c8c88367dd09419b6f0c4225479170c58d6e6a2a298343dab513950cf4a1e1836626d06036f386b6d033a119ce5e437b1d946051775d7ec6279a37deb6815907cea1e2d205283cfad244245a73f0a3f241c7e8cd420e9373b8e6871ba5221e8e1d061ed04c07818f40b172d22cd8feecad3b07c42af07c7aaf50efb7d78ce3d3f1ec1e4b5c90e5bf529c4b63a8a52b8e819f8efb0e196a58d4547b7d12599c29be488d9955293a6834ecb310b85694db5be4dbbd817dc6ae15b69b44c36f1a03ae240852c6ea57b6e24649b79b1d4ca9a730a0697f79a5ec3b490587a560a79b21ab6429a6f9f2bced83371bb26442fccbbe5d40a89a7faec558308480a39ed737b55baa6d024688c8477387bbed32cdf8696b962aea1d6be4ecdf2db5b9fc0d2a039a820828b28b92cf20d9fdf3724dc9fcae6c4f2dd4aae38a6e6e1b628b12dde6e15b803dad4b9ce43da7885af493439a3621f0499643e10cf1fabda4e7ddd915fb5bee763e52d807fa310449dbb58b93322b91fcbea27cd098aaa8284e3bee938f41c9390a7104b279071b7db863bceb1c772078b9237b8836f541a3faec3097e5d528ffefc2e449031f32cf63a6fc7a4fcb1cbe590d053d8d37fe8566942bb21bf57bd5774d0742462d9fe2e95f6c63b586ef0483ce9fb9382c5c50a137458b19511b980035334478a7921f5685160218bbc38366825c4930a6af1090bc8496f6c733ec36013e36e17b188beebdb4c6d2a40832e75f5f77f4f798a4df6084b86c558669758d1dde859b7aec315df6572a02aedac79c4447d3ba8edfd2fa5d0fdad982d52dcdf8958f57eff4423f87f35f8c0542973a6fe062b0359a582159304dd4e5ca5e69b5b04a6463739420ba3a18b7c6bb92143d6cc0b3286359e2ead0a6834bbdab3c527395052ed97e57df2372ffc9e5d0d3bf97eeb57bf926168d5074c954e8adcb87c54a64600660b7265ba6ea8b4535f43be4e790f88ba3491ff5e2b882f5c3aaf63086f847ce6ee6cb44550f68f37f4c1e1028dc99b374664659c2f4c46e34a0e71a46ff3b268737f2439533758d47db58c9e5ce34cbc8c8bf659340f8f1d87d07026040665282013a3ef28e2dcdc2ad13743783adfb9c97795b061cf5ef89cc23a1461f2e5435119980966ae5d8a90e4823b6775d5277292a6f348683dd9db3c8137cf1d8811de45888fb92b86a30ef8b703ffe5af32e2eb4452448cd0515649904d10e505bbf3adf7f431c768e9f3fd2d4bf1dc8a29a8f827282ad3172c14ba9b136fbf64a9343eb9bc4385fc910a5d349c51c555fe4a19acc9826358c32a9d316ba7b4bc377d74741c9d749755eb34616abab1fb815aad6bb38befcdc6f1536d07753d5d3312867e6393134672ceb353f5afbc1830364354414407f4a66b9570c8ca3a09037b64887d1e8bfed6b32788ee1893c0e7dc0eee441c0c9ad71365428f93b6c93b90be4a0f11b73d217c15da4934e2fe4fd88715492be1eb332a93067fbc96d391261b5c4102fc35db8f7dc2b92e38e97095df0577d76869d4a0296c32b545850df706618a6cdef3fde56bcf147e293044e10f26ddf1ac88365e48cdca8facb145a6f068bd95c4eb81987b7261a42fada963e5e6e4b0a2144b6be03fdebacbc3555dae6df998a0b7450a38050e812c4f72f15ed88a2b8baa7466f0025314db87d38a5b37f4d6a801274abd074779a94cd2cf3454d82c3782b0c99e4c80a70f4f519ddf7138f3bfd72095df9f74a7d5c8b814087065d002f1453c736990e7c4ab05ed58a18d805d139cd2f1b6e42b12f246ff52a7954973441704918057a563b2a9b05d5b857e52fd247c0fe14ae163de9294c6e338df53e315065e9ab33b6993c5019caf1648d2c410c1064a5c19f3989f5636896e1d62eacd2205aacbe12fae89524045c729cdd53d3a45f9817dcb4a12dbbc3adb26bbe9d3f7a95742886523d54bd564518dd1772b1c89e7ddf7b601dce6d78914e554f8041dc47eb858a24b432568d6c85bb531f62d368fee58132ff29bda802a4d493999c2d9b87bb43a9edc6c33fe89b6e2668d1fdbb2e4a088cb50612a8eb70cc69ee13feb6d7c4b5760de6d36e816b543a39e2850a5bba47ab39edeb3e9eae0bb7656394a67be79fac27fd88f04406bca6ac566f02a6fa250865bf9725490e0843fc57da1c10c4e455e779448f7ce1b7d521914fd8910ef59908d223358192b094f7ba9ebc7f1a01fad4bbbfdf148f727aa167ff5aaa4c4bfd6c365ebb54c31020f323778f78382b553bf7645d3e7e59d050684a8bbe276e4718852263d7c03e5bf3622aea5ba9e57ed41cd80a0b00c795012002b16ce85d37997d93ab56c3d3b47dc54b74894ca81e82667d97f2b73c69e177cb672fc4d58e4cc40ef4a53be9fd93a01698470fc0b0ca5d19cf8a6c2760d7a161d8e3e5173f3b321b64fb994bf0f4ecfe72e0658db71c52b7b2c0624ae6c517cd44e35db975ef43282fc9249cde788b177dbd16127d0a12b6e398e69b7ed1c64fdeea1f9abc3ca5efc5f1cd44ed1c900f959c57fc54c30b5318dd875518282a53315e34a4d2bccc96effaa2f3024198222632a43ae1dbd22bdd7f4006e3803e7515d20aeaf4a6b4c93eb4c164094b9593dc0d47a5d84509093df1d2d15ec471c10cd347e153510dfc8561b4aab6fddc0a9d4fa8b1de8109f494ca59be3ef1034ee930d1a90ed877a5e2728cefd1295937f405bd80571943e7be631e39181b1c3c06502f2a15047625e8c0d8ab853f9cccd41ca9090ce47a7a29e07863c7b2de24b20439aabc2dacffe07602ebf5e7d322117ef4c23a6886f5f59c470cbf1047e82016fb36f9c3b0dfedaf30f6cf116d4202fcf7e24a80f3596ce38f4e8ec882399ecbf16063bbd00c2fce18fe739715fb6d868a40c1907cb27217f649402c915f5d50cdd87d92ad163afcd1b8e26ed5086331ca99b862340f00ebe3c7bb209fa27c4dbd184a0d80535230e173b0e07ddac40d3b4f2bc56649ea9c4450b8625802197abf739ac1ee6c976e5a044ce17366e442a0cfc876e6dfc6c9b193997d5c09dcc7a5fbf9287c9c838d4847ff37f45d74ef9fa0f37cf0d42ee0ebb1a4487c79ac83d9f0e4e805f89e551bb4cdff4fe6faefed551bf71789f894edc8822aab725b19bd649556d590c3806a6f8a83f3794d3002033afe03c6337f127eb08ac400f27fd9e1131aef586b109b499ea54004249fde5f3acdc8507bb30b613eb47506c287ba443274bc212de5962779b7ebc75b6842414f8091c0f00e41652163291ffeb435f61d1885de2aabf3b9ba6057fb880b80867734e356138d8715be708682f4939e2dedd6701bf98845b93a35389f175d569b5411f0cd93d699faf5c5852897794081bec9ae9c70d6173895a2bcdcccdf7e441c4dd318ccc51c36f6f3c57bde604ae029781bd7c14f5ca5eb993fe2a44175cb6a766d9e67ea0845596747db284830157abb6cf3ab02da31b63dbeeb7ce66b052eebd24bafc34aa1b28367d9a0165f6cd441f5b2826e00403e0340ca738c983760650abb434fe0219e06d7b635097034d11c14532c1367e2a822c4a419e6ba349339131c3a954a09ccd46fd047f048bcc3d02d2bcfffe974858dc5f6311f5e6828f34fb993120d95362e7db1c5a29a3c685e02cecc0ac1092fd80da44d727146fcde8bcdcd9e4058098faa3f9e37167832f2f1c546fb1e0ebd18fdb935a561fd22029283707662fbce379c129cd81d0402964c1ea0efd717cc06e1f319ad4effbaf40365d22f27a74ce567fb76b3432a9026eb857a3f7e11ff0cee63a836f839ffb4a43f004f673567225ea5d637611d1e7df405f3b455724c3b90966354b22a7ebde614176536401ef71bb6b214a8c79de571aa842a9d2ed6568cc5e928751f89c8548809f01f581a032cf4cb8754c50077acde129baa0f4c5a135b36eda45e62e82d874cc3ac6a5197d7ddc7a6362b8a4bf6319045ccca415be83119343da58e79e78d357c37164e3cb35051a49ec4569d94429d05463712e5ba711032b712deebf140e27267eb6c8c53adf7fd45b22059cc</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">输入密码</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css"><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/04/02/%E9%A1%B9%E7%9B%AE%E5%9C%BA%E6%99%AF%E6%89%A9%E5%B1%95-%E4%BA%8C/</id>
    <link href="https://www.sekyoro.top/2026/04/02/%E9%A1%B9%E7%9B%AE%E5%9C%BA%E6%99%AF%E6%89%A9%E5%B1%95-%E4%BA%8C/"/>
    <published>2026-04-02T13:55:35.000Z</published>
    <summary>接着上一篇，介绍数据一致性，可用性等.</summary>
    <title>项目场景扩展(二)</title>
    <updated>2026-04-07T10:39:31.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="OpenClaw" scheme="https://www.sekyoro.top/tags/OpenClaw/"/>
    <category term="Agent" scheme="https://www.sekyoro.top/tags/Agent/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近OpenClaw(原moltbot)实在是大火了一把，有如当时DeepSeek的国内热潮。但是DeepSeek模型的训练、微调包括前端的数据集收集和清理等都不是一般人能负担的，而OpenClaw不仅代码开源，在Agent开发的工程技术上也提供了一些实践，由此引入了一波”养虾潮”。</p><span id="more"></span><p>早期Langchain,llamaIndex以及AutoGen等都封装了一系列工具调用、记忆机制、分块策略以及结构化输出等。</p><blockquote><p>目前langchain团队有Deep Agents,LangGraph,LangSmith以及LangChain等开源框架,都与AI应用开发与部署相关。</p></blockquote><p>但这些框架都是用于开发AI产品的，实际的AI应用又往往依附具体平台。OpenClaw在我看来的一个优势就是开源，使得其与多个IM系统通信(当然，前提是这些IM公司也愿意支持)，更加实用。下面具体介绍其重要组件。</p><h1 id="网关"><a href="#网关" class="headerlink" title="网关"></a>网关</h1><p>Gateway 是一个<strong>常驻后台的消息路由服务</strong>。它负责接收各个客户端的消息，例如QQ,微信，发送给Agent，然后再分发响应。核心思想是“<strong>单网关、多端接入</strong>”。它不像传统的单一程序，而是一个分布式设计的通信枢纽。具体来说，通过websocket并约定消息格式与clients通信。</p><p><strong>唯一性</strong>：一台主机上只能运行一个 Gateway 进程。</p><p><strong>职责</strong>：它统一管理所有的通信插件（WhatsApp, Telegram, Slack, Discord, Signal 等）。它是唯一一个直接维护这些第三方平台 Session 的地方。</p><p><strong>通信</strong>：通过 WebSocket（默认端口 <code>18789</code>）与外界沟通。</p><p><img data-src="https://files.seeusercontent.com/2026/03/24/y4qO/image-20260324145811017.png" alt="image-20260324145811017"></p><p>虽然都连接到 Gateway，但身份不同：</p><ul><li><strong>Clients (客户端)</strong>：如 Mac 应用、CLI 命令行工具或 Web 界面。它们发送命令（如“发消息”），并接收事件（如“助手正在回复”）。</li><li><strong>Nodes (节点)</strong>：这是 OpenClaw 的特色。你可以把你的手机（iOS/Android）或另一台电脑作为“Node”接入。它们提供硬件能力，比如调用摄像头 (<code>camera.*</code>)、获取位置 (<code>location.get</code>) 或屏幕录制。</li><li><strong>WebChat</strong>：一个简单的网页端聊天界面，直接通过 WebSocket 接口与网关通信</li></ul><h1 id="运行时"><a href="#运行时" class="headerlink" title="运行时"></a>运行时</h1><p>Agent 运行必须有一个工作区目录（默认在 <code>agents.defaults.workspace</code>）。</p><ul><li><strong>唯一性</strong>：这是 Agent 唯一的“工作目录 (CWD)”。</li><li><strong>作用</strong>：所有的工具操作、文件读取、上下文注入都发生在这里。</li></ul><p>当一个Agent启动时，它会干哪些事情？</p><p>首先，OpenClaw包含一系列配置文件，工作区文件<code>~/.openclaw/openclaw.json</code>配置启动端口和模式等。另外工作区目录还包含一系列md文件用于设置Agent个性、工作流程和使用的工具等。</p><ul><li><strong><code>AGENTS.md</code></strong>：核心运行指令和“长期记忆”。</li><li><strong><code>SOUL.md</code></strong>：定义 Agent 的性格、语气和行为边界（灵魂）。</li><li><strong><code>IDENTITY.md</code></strong>：Agent 的名字、图标和基本人设。</li><li><strong><code>USER.md</code></strong>：你的个人信息，方便 Agent 称呼你。</li><li><strong><code>BOOTSTRAP.md</code></strong>：初次运行时的引导流程（完成后会被自动删除）。</li></ul><p><strong>注入逻辑：</strong> 在每个会话的第一轮，OpenClaw 会把这些文件的内容直接塞进模型的 <strong>Context</strong> 里。空文件会被跳过。大文件会被截断并加上标记，以便提示信息保持简洁.如果文件缺失，OpenClaw 会注入一条“缺失文件”标记行.</p><p>会话记录以 JSONL 格式存储在：<code>~/.openclaw/agents/&lt;agentId&gt;/sessions/&lt;SessionId&gt;.jsonl</code></p><h2 id="工具-Tools-与-技能-Skills"><a href="#工具-Tools-与-技能-Skills" class="headerlink" title="工具 (Tools) 与 技能 (Skills)"></a>工具 (Tools) 与 技能 (Skills)</h2><ul><li><strong>内置工具</strong>：如 <code>read</code>（读）、<code>write</code>（写）、<code>exec</code>（执行命令）等。这些是 Agent 与系统交互的基础。</li><li><strong>技能 (Skills)</strong>：这是更高级的功能模块，可以从三个地方加载：<ol><li>软件自带。</li><li>用户本地路径 (<code>~/.openclaw/skills</code>)。</li><li>工作区内部路径。</li></ol></li></ul><p>OpenClaw 的 Agent 运行时是建立在 <strong>Pi agent core</strong> 之上的，它负责：</p><ul><li><strong>会话管理</strong>：对话历史存放在 <code>~/.openclaw/agents/&lt;agentId&gt;/sessions/</code> 下，以 <code>.jsonl</code> 格式存储。</li><li><strong>流式转向 (Steering while streaming)</strong>：这是个高级功能。当 Agent 正在说话或执行工具时，如果你发送新消息，OpenClaw 可以配置如何“插嘴”或“引导”它。</li></ul><h1 id="Agent-Loop"><a href="#Agent-Loop" class="headerlink" title="Agent Loop"></a>Agent Loop</h1><p><strong>Agent Loop（智能体循环）</strong> 是将静态的 AI 模型转化为动态“智能体”的核心机制。它不只是简单的一问一答，而是一个<strong>自动化的、闭环的执行过程</strong>。</p><p>Agent Loop 是 Agent 运行的“真实”生命周期：从接收消息到执行动作，再到最终回复。 在 OpenClaw 中，这通常是一个<strong>序列化（Serialized）</strong>的过程，确保每个会话（Session）在同一时间只有一个逻辑在运行，防止工具调用产生冲突或竞态条件。</p><p>一次完整的循环包含以下五个关键步骤：</p><ol><li><strong>输入与归一化 (Intake)</strong>：<ul><li>接收来自 WhatsApp、Telegram 等渠道的消息。</li><li>将语音、图片等非结构化数据转化为模型可理解的文本和附件。</li></ul></li><li><strong>上下文组装 (Context Assembly)</strong>：<ul><li>动态加载 <code>AGENTS.md</code>、<code>SOUL.md</code> 和会话历史。</li><li>注入当前可用的 <strong>Tools（工具）</strong> 和 <strong>Skills（技能）</strong> 列表。</li></ul></li><li><strong>模型推理 (Model Inference)</strong>：<ul><li>将组装好的上下文发送给大模型（如 GPT-4o 或 Claude）。</li><li>模型决定：是直接回复用户，还是需要<strong>调用工具</strong>。</li></ul></li><li><strong>工具执行 (Tool Execution)</strong>：<ul><li>如果模型请求调用工具（如 <code>exec</code> 运行代码或 <code>read</code> 读取文件），OpenClaw 的后端会代为执行。</li><li><strong>结果回填</strong>：将工具执行的输出（Output）作为新的上下文反馈给模型。</li></ul></li><li><strong>持续迭代与持久化 (Loop &amp; Persistence)</strong>：<ul><li>模型看到工具结果后，可能决定发起下一次工具调用，或者结束循环。</li><li>所有的交互过程（包括隐藏的工具调用）都会存入 <code>.jsonl</code> 会话文件</li></ul></li></ol><p>为了保证稳定性，OpenClaw 引入了 <strong>Lane Queue（车道队列）</strong> 系统：</p><ul><li><strong>顺序执行</strong>：默认情况下，同一会话的任务是按顺序排队的。这防止了 Agent 在还没读完文件 A 的时候就开始修改文件 A。</li><li><strong>三种模式</strong>：<ul><li><code>collect</code>：收集一段时间内的消息，一次性处理。</li><li><code>steer</code>：在 Agent 运行过程中注入新指令（转向）。</li><li><code>followup</code>：当前任务结束后立刻开始下一个任务。</li></ul></li></ul><p>OpenClaw 的 Loop 支持 <strong>Partial Replies（部分回复）</strong>：</p><ul><li>当模型在“思考”或调用长耗时工具时，它会向客户端流式推送事件。</li><li><strong>Block Streaming</strong>：可以将回复拆分成段落，每完成一段就发送一次，提升用户感知的响应速度。</li></ul><h1 id="系统提示词"><a href="#系统提示词" class="headerlink" title="系统提示词"></a>系统提示词</h1><p><strong>系统提示词</strong> 是 OpenClaw 每次运行智能体时自动构建的“操作指令集”。它不像普通的聊天机器人那样只有一段固定的文字，而是由多个<strong>动态模块</strong>拼装而成的。</p><p>OpenClaw 的系统提示词是<strong>权属归网关（Gateway）所有</strong>的，意味着它会根据你当前的运行环境（时间、工作区文件、已安装的技能）实时生成。其核心目标是在有限的上下文窗口内，为模型提供最精准的“行动指南”</p><p>一个标准的 OpenClaw 系统提示词通常按顺序包含以下板块：</p><ul><li><strong>Tooling (工具集)</strong>：当前可用工具的列表及其简短描述。这是 Agent 能“干活”的前提。</li><li><strong>Safety (安全守则)</strong>：防止模型产生越权行为或试图绕过监管的简短提醒。</li><li><strong>Skills (技能索引)</strong>：告诉模型目前有哪些技能可用，以及如何通过 <code>SKILL.md</code> 按需加载详细指令。</li><li><strong>Workspace (工作区)</strong>：明确告知模型当前的运行目录（<code>agents.defaults.workspace</code>）。</li><li><strong>Project Context (项目上下文)</strong>：自动注入工作区中的引导文件（如 <code>AGENTS.md</code>、<code>SOUL.md</code>、<code>USER.md</code>）。</li><li><strong>Runtime Metadata (运行时元数据)</strong>：包含当前时间（本地/UTC）、主机操作系统、所用模型名称、当前的“思考深度”等级。</li></ul><p>上下文注入逻辑，OpenClaw 会扫描工作区，并将特定的 Markdown 文件内容注入提示词：</p><ul><li>如果文件<strong>存在</strong>：内容会被读入，如果文件太大（超过 20,000 字符），系统会自动截断并加上 <code>[TRUNCATED]</code> 标记。</li><li>如果文件<strong>缺失</strong>：系统会注入一行“Missing file”标记，提示模型该部分信息缺失。</li></ul><h1 id="上下文"><a href="#上下文" class="headerlink" title="上下文"></a>上下文</h1><p>简单来说，Context 就是 <strong>OpenClaw 在单次运行中发送给 AI 模型的所有信息总和</strong>。</p><p>Context 受到模型“上下文窗口”（Token 限制）的约束。它不同于“Memory（存储在磁盘上的长期记忆）”，Context 仅指<strong>当前模型窗口内可见的内容</strong>。</p><p>它主要由三部分组成：</p><ul><li><strong>系统提示词 (System Prompt)</strong>：由 OpenClaw 自动构建，包含规则、工具列表、技能列表、当前时间、运行状态以及注入的工作区文件。</li><li><strong>对话历史 (Conversation History)</strong>：当前会话中你与助手的往返消息。</li><li><strong>工具调用与附件</strong>：命令的输出结果、读取的文件内容、图片/音频等</li></ul><p>每次运行都会重新构建系统提示词，其中最特别的是 <strong>项目上下文 (Project Context)</strong>。它会自动注入工作区中的特定文件（如果存在）：</p><ul><li><code>AGENTS.md</code> / <code>SOUL.md</code>（定义性格/灵魂）</li><li><code>TOOLS.md</code> / <code>IDENTITY.md</code>（定义工具和身份）</li><li><code>USER.md</code>（用户信息）</li><li><code>BOOTSTRAP.md</code>（首次运行引导）</li></ul><blockquote><p><strong>注意：</strong> 为了防止撑爆上下文，大文件会被自动截断（默认每个文件 20,000 字符）。</p></blockquote><p>注意skills和tools的差别，两者对上下文窗口的占用：</p><ul><li><strong>技能 (Skills)</strong>：系统提示词只包含技能的名称和简短描述。模型只有在需要时才会去按需读取具体的 <code>SKILL.md</code> 指令，这样可以节省空间。</li><li><strong>工具 (Tools)</strong>：工具的开销是双重的。一是系统提示词里的文字描述，二是 <strong>JSON Schema（工具定义）</strong>。即使你在对话中看不见这些 JSON 代码，它们依然会占用大量的 Token。</li></ul><p>可以通过斜杠命令实时监控和优化上下文：</p><ul><li><code>/status</code>：快速查看当前窗口有多满。</li><li><code>/context list</code>：列出当前注入的文件及其大小，查看是否被截断。</li><li><code>/context detail</code>：深入分析哪些工具或技能占用了最多的空间。</li><li><code>/compact</code>：<strong>压缩指令</strong>。将旧的对话历史总结为一个精简的条目，从而释放窗口空间。</li></ul><h1 id="上下文引擎"><a href="#上下文引擎" class="headerlink" title="上下文引擎"></a>上下文引擎</h1><p>Context Engine 是一个可插拔的模块，它的唯一任务是：<strong>把散落在各处的数据（对话历史、文件、工具结果）按照优先级和 Token 限制，拼成最终发给模型的那个长字符串。</strong></p><p>当模型准备运行（Run）时，上下文引擎会执行以下操作：</p><ul><li><strong>分层堆叠</strong>：它按照预定义的顺序堆叠内容：<code>系统提示词</code> -&gt; <code>项目上下文文件</code> -&gt; <code>被裁剪的历史记录</code> -&gt; <code>最新的用户消息</code>。</li><li><strong>Token 预算分配</strong>：它会计算每个部分占用了多少 Token。如果超标，它负责决定<strong>先扔掉谁</strong>。<ul><li><em>通常策略：</em> 优先保留系统指令和最近的几轮对话，裁剪掉中间陈旧的工具执行结果</li></ul></li></ul><h2 id="压缩-Compaction"><a href="#压缩-Compaction" class="headerlink" title="压缩 (Compaction)"></a>压缩 (Compaction)</h2><p>这是解决“记不住事”和“Token 太贵”的核心技术。</p><ul><li><strong>触发时机</strong>：当对话历史接近模型上限（比如 32k 的 80%）时。</li><li><strong>执行方式</strong>：引擎会调用一个后台模型，把旧的 10 轮对话<strong>总结成一段话</strong>，然后替换掉原来的 10 轮细节。</li><li><strong>持久化</strong>：压缩后的总结会存入 <code>.jsonl</code> 会话文件中，确保下次加载时依然有效。</li></ul><h1 id="Agent-Workspace"><a href="#Agent-Workspace" class="headerlink" title="Agent Workspace"></a>Agent Workspace</h1><p><strong>Agent Workspace（智能体工作区）</strong> 是智能体赖以生存的“物理世界”。如果把智能体比作一个程序员，那么 Workspace 就是他的这台电脑和当前打开的项目目录。它是所有工具操作、文件读写和上下文注入的<strong>唯一基准点</strong>。</p><p>在 OpenClaw 启动时，它会指定一个目录作为 <code>agents.defaults.workspace</code>。</p><ul><li><strong>封闭性</strong>：智能体的所有相对路径操作（如 <code>read(&quot;file.txt&quot;)</code>）都相对于这个目录。</li><li><strong>权限限制</strong>：出于安全考虑，智能体通常被禁止跳出这个目录去修改系统文件</li></ul><p>一个健康的工作区通常包含以下几个关键部分：</p><div class="table-container"><table><thead><tr><th><strong>文件/目录</strong></th><th><strong>作用</strong></th></tr></thead><tbody><tr><td><strong><code>AGENTS.md</code></strong></td><td>长期指令、任务清单和重要记忆。</td></tr><tr><td><strong><code>SOUL.md</code></strong></td><td>核心性格、语气准则和行为边界。</td></tr><tr><td><strong><code>USER.md</code></strong></td><td>关于你的信息（偏好、姓名、习惯）。</td></tr><tr><td><strong><code>TOOLS.md</code></strong></td><td>你对工具使用的特殊要求（例如：“用 <code>curl</code> 时必须加 <code>-L</code>”）。</td></tr><tr><td><strong><code>skills/</code></strong></td><td>存放所有 <code>SKILL.md</code> 的文件夹。</td></tr><tr><td><strong><code>scratchpad/</code></strong></td><td>智能体存放临时文件、代码片段或中间数据的地方</td></tr></tbody></table></div><h3 id="动态注入逻辑"><a href="#动态注入逻辑" class="headerlink" title="动态注入逻辑"></a>动态注入逻辑</h3><p>在 OpenClaw 的架构中，并没有一个持久不变的“大脑状态”。每一轮你发消息给 Agent，<strong>Context Engine（上下文引擎）</strong> 都会执行一次“组装”动作：</p><ul><li><strong>第一轮对话（开启新会话）</strong>：<ul><li>读取 <code>AGENTS.md</code>, <code>SOUL.md</code>, <code>USER.md</code> 等全部内容。</li><li>如果是新工作区，还会读取并执行 <code>BOOTSTRAP.md</code>。</li><li><strong>结果</strong>：Agent 处于最清醒、最完整的人设状态。</li></ul></li><li><p><strong>连续的后续对话（第 2, 3… N 轮）</strong>：</p><ul><li><strong>依然会读取</strong> <code>SOUL.md</code> 和 <code>AGENTS.md</code>（确保人设不走样）。</li><li><strong>叠加</strong> 之前的对话历史（Transcript）。</li><li><strong>按需读取</strong> 之前提到过的 <code>SKILL.md</code>（如果还没被裁剪掉）。</li></ul><p><strong>System Prompt</strong> 的来源：</p></li><li><p><strong>启动扫描</strong>：每次对话开始前，OpenClaw 会扫描工作区。</p></li><li><strong>自动读取</strong>：它会自动读取上述 <code>.md</code> 文件并拼装进提示词。</li><li><strong>截断机制</strong>：如果文件太大（默认超过 20,000 字符），OpenClaw 会执行“安全截断”，防止 Token 溢出。</li></ul><p>OpenClaw 引入了 <strong>Sandbox</strong> 概念来保护你的工作区：</p><ul><li><strong>Main Workspace</strong>：主会话使用的默认目录。</li><li><strong>Session Workspace</strong>：如果开启了沙箱模式，每个新会话可以拥有自己独立的子目录（在 <code>workspaceRoot</code> 下）。这防止了不同任务之间互相“污染”代码或配置。</li></ul><h2 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h2><p>当你第一次启动 Agent 时，OpenClaw 会在你的工作区（默认 <code>~/.openclaw/workspace</code>）执行以下动作：</p><ol><li><strong>投放种子文件</strong>：自动生成 <code>AGENTS.md</code>、<code>IDENTITY.md</code>、<code>USER.md</code> 和关键的 <strong><code>BOOTSTRAP.md</code></strong>。</li><li><strong>开启 Q&amp;A 仪式</strong>：Agent 会主动问你一系列问题（一次一个），比如“你希望我怎么称呼你？”、“我的主要任务是什么？”。</li><li><strong>写入“记忆”</strong>：它会把你的回答实时写入到 <code>IDENTITY.md</code>（它的名字/人设）、<code>USER.md</code>（你的偏好）和 <code>SOUL.md</code>（它的语气/边界）中。</li><li><strong>焚毁指令</strong>：一旦仪式完成，<strong><code>BOOTSTRAP.md</code> 会被自动删除</strong>，确保这个过程不会在下次启动时重复运行</li></ol><h1 id="会话与记忆机制"><a href="#会话与记忆机制" class="headerlink" title="会话与记忆机制"></a>会话与记忆机制</h1><p><strong>Session（会话）</strong> 是系统管理对话状态、隔离上下文和持久化记忆的核心单位。</p><p>OpenClaw 对 Session 的持久化分为两个层面，确保了即使程序重启，对话和状态也不会丢失：</p><ul><li><strong>Session Store (<code>sessions.json</code>)</strong>：<ul><li>存储所有会话的<strong>元数据</strong>（如 Session ID、最后活跃时间、使用的模型、当前的 Token 计数等）。</li><li>这是网关（Gateway）快速索引会话的“账本”。</li></ul></li><li><strong>Transcript (<code>&lt;sessionId&gt;.jsonl</code>)</strong>：<ul><li>存储真实的<strong>对话记录</strong>。</li><li>采用 <strong>JSONL（每行一个 JSON 对象）</strong> 格式，包含用户消息、助手回复以及隐藏的工具调用过程。</li><li>这是一个“树状结构”，每一条记录都有 ID 和父级 ID，支持复杂的追溯。</li></ul></li></ul><h3 id="会话隔离与路由-Routing"><a href="#会话隔离与路由-Routing" class="headerlink" title="会话隔离与路由 (Routing)"></a>会话隔离与路由 (Routing)</h3><p>OpenClaw 通过 <code>sessionKey</code> 来决定一条新消息应该进入哪个会话：</p><ul><li><strong>私聊 (Direct Messages)</strong>：通常合并为一个主会话（<code>main</code>），由 <code>session.dmScope</code> 参数控制。</li><li><strong>群组/频道 (Groups/Channels)</strong>：每个群组或频道通常会获得独立的 <code>sessionKey</code>，确保不同群组之间的对话上下文不会互相污染。</li><li><strong>线程 (Threads)</strong>：在 Slack 或 Discord 等支持线程的平台上，每个 Thread 也会被视为独立的 Session。</li></ul><p>Session 并不是永恒不变的，它有明确的<strong>刷新机制</strong>：</p><ul><li><strong>手动重置</strong>：通过 <code>/new</code> 或 <code>/reset</code> 命令强制开启一个全新的 Session ID。</li><li><strong>每日自动重置</strong>：默认在当地时间 <strong>凌晨 4:00</strong> 进行。如果最后一次活动早于这个时间，下一条消息将自动触发新会话。</li><li><strong>闲置重置</strong>：可以配置 <code>idleMinutes</code>，如果在设定的时间内没有互动，会话将过期。</li></ul><p>为了防止 Session 文件无限增长导致性能下降，OpenClaw 会执行：</p><ul><li><strong>自动压缩 (Auto-Compaction)</strong>：当会话接近模型的上下文窗口限制时，引擎会运行一个“静默轮次”，总结重要信息并清理冗余的工具调用细节。</li><li><strong>持久化笔记</strong>：在丢弃细节前，Agent 会被提醒将关键事实写入 <code>AGENTS.md</code> 或 <code>USER.md</code>，实现从“短期会话”到“长期记忆”的转化。</li></ul><h1 id="会话裁剪"><a href="#会话裁剪" class="headerlink" title="会话裁剪"></a>会话裁剪</h1><p><strong>Session Pruning（会话裁剪）</strong> 是一种为了节省 Token 成本、优化缓存效率而设计的“内存即时清理”机制。它和你之前看到的 <strong>Compaction（压缩）</strong> 有本质区别：压缩是永久性的总结，而裁剪是<strong>临时性的剔除</strong>。</p><p><strong>即时清理，不改存档</strong></p><ul><li><strong>不改硬盘</strong>：裁剪只发生在发送给 AI 模型的那一瞬间。它<strong>不会</strong>修改你硬盘里的 <code>.jsonl</code> 会话文件。</li><li><strong>目标对象</strong>：它专门针对旧的 <strong>Tool Results（工具执行结果）</strong>。用户和助手的对话文字永远不会被裁剪。</li></ul><p><strong>为什么需要裁剪？</strong>（主要针对 Anthropic 模型）</p><p>这个功能主要是为 Anthropic（如 Claude 系列）的 <strong>Prompt Caching（提示词缓存）</strong> 优化的：</p><ul><li><strong>节省写缓存成本</strong>：如果一个会话闲置了一段时间，缓存会失效。下次请求时，如果不裁剪掉那些冗余的旧工具输出（比如之前执行过的一大段代码运行结果），你就得为这些旧数据的重新写入付全额的钱。</li><li><strong>TTL 机制</strong>：默认情况下，如果距离上次调用超过 5 分钟（<code>ttl: &quot;5m&quot;</code>），裁剪就会触发，清理掉不再需要的旧数据，只保留最近几轮的上下文。</li></ul><p><strong>裁剪的两种手段</strong></p><ul><li><strong>Soft-trim（软裁剪）</strong>：针对超大的工具输出，只保留开头和结尾，中间用 <code>...</code> 代替。</li><li><strong>Hard-clear（硬清理）</strong>：直接把整个工具输出替换为 <code>[Old tool result content cleared]</code>。</li></ul><p><strong>裁剪的保护规则</strong>（哪些不删）</p><p>OpenClaw 非常谨慎，以下内容会被保护：</p><ul><li><strong>最近的消息</strong>：默认保留最后 3 轮助手的对话（<code>keepLastAssistants: 3</code>），确保模型不会因为丢失最近的工具结果而变傻。</li><li><strong>多媒体内容</strong>：包含图片的工具结果<strong>永远不会</strong>被裁剪。</li><li><strong>对话文字</strong>：你和 Agent 说的话永远是完整的。</li></ul><div class="table-container"><table><thead><tr><th><strong>维度</strong></th><th><strong>Session Pruning (裁剪)</strong></th><th><strong>Compaction (压缩)</strong></th></tr></thead><tbody><tr><td><strong>持久性</strong></td><td><strong>暂时的</strong>（仅限本次请求）</td><td><strong>永久的</strong>（写入历史文件）</td></tr><tr><td><strong>内容变化</strong></td><td>只是<strong>删除</strong>旧的工具输出</td><td>将旧对话<strong>总结</strong>成一小段话</td></tr><tr><td><strong>主要目的</strong></td><td>降低缓存写入成本、保持窗口整洁</td><td>彻底释放 Token 空间，解决长时记忆问题</td></tr></tbody></table></div><h1 id="会话工具"><a href="#会话工具" class="headerlink" title="会话工具"></a>会话工具</h1><p><strong>Session Tools（会话工具）</strong> 是一组专门为 AI Agent 设计的功能，允许 AI 能够“感知”并“操作”不同的对话上下文。简单来说，这套工具让 AI 不再局限于当前的这一段对话，而是可以查看其他历史记录、列出活跃会话，甚至主动给另一个会话发送消息。</p><h3 id="核心工具集"><a href="#核心工具集" class="headerlink" title="核心工具集"></a>核心工具集</h3><p>AI 可以调用以下四个具体的函数：</p><ul><li><strong><code>sessions_list</code></strong>：列出当前的会话。AI 可以通过它看到有哪些活跃的群聊、私聊或任务（Cron）。</li><li><strong><code>sessions_history</code></strong>：获取指定会话的历史记录（Transcript）。这对于 AI 回溯之前的决策或获取跨会话的信息至关重要。</li><li><strong><code>sessions_send</code></strong>：向另一个会话发送消息。这实现了 <strong>“Agent 间通信”</strong>，例如一个负责调度的 AI 让另一个负责执行的 AI 去干活。</li><li><strong><code>sessions_spawn</code></strong>：产生一个<strong>隔离的子会话</strong>。AI 可以创建一个“分身”去处理特定的子任务，处理完后再把结果汇报回来，而不会污染主会话的上下文</li></ul><p>为了准确操作，OpenClaw 为不同类型的会话定义了唯一的 Key：</p><ul><li><strong><code>main</code></strong>：指代当前 Agent 的主对话。</li><li><strong><code>agent:&lt;id&gt;:&lt;channel&gt;:group:&lt;id&gt;</code></strong>：群组对话的标识。</li><li><strong><code>cron:&lt;id&gt;</code></strong>：定时任务产生的会话。</li><li><strong><code>node-&lt;id&gt;</code></strong>：特定节点（如硬件设备）的会话。</li></ul><h1 id="记忆机制"><a href="#记忆机制" class="headerlink" title="记忆机制"></a>记忆机制</h1><p><strong>Memory（记忆）</strong> 机制并不是指 AI 模型本身的参数，也不是简单的数据库存储，而是一套<strong>基于文件系统的持久化长短期记忆系统</strong>。</p><p>OpenClaw 将记忆存储在配置的工作空间（如 <code>/root/.picoclaw/workspace</code>）中，采用人类可读的 <strong>Markdown</strong> 格式：</p><ul><li><strong>短期/日常记忆 (<code>memory/YYYY-MM-DD.md</code>)</strong>：<ul><li>这是一个“追加模式”的日志。</li><li>每天会自动生成一个文件，记录当天的重要事件、对话摘要或运行笔记。</li><li>系统在每次会话开始时，默认会读取“今天”和“昨天”的日志。</li></ul></li><li><strong>长期/核心记忆 (<code>MEMORY.md</code>)</strong>：<ul><li>存放经过整理的、长期有效的客观事实、用户偏好或关键决策。</li><li>AI 会在需要时检索此文件。</li></ul></li></ul><p>AI 并不是“被动”拥有记忆，而是通过以下两个工具主动管理：</p><ul><li><strong><code>memory_search</code></strong>：语义搜索。即使 AI 问的问题和记录的文字不完全一样，系统也会通过向量索引（Vector Index）找到最相关的片段。</li><li><strong><code>memory_get</code></strong>：精准读取。AI 可以指定读取某个 Markdown 文件的特定行。</li></ul><p><strong>自动冲刷机制</strong></p><p>这是 OpenClaw 的一个精妙设计。由于 AI 的对话上下文（Context Window）有限，当对话太长需要进行“压缩（Compaction）”时，系统会执行以下操作：</p><ol><li><strong>静默触发</strong>：在正式丢弃旧对话之前，系统会给 AI 发送一个隐藏指令。</li><li><strong>整理笔记</strong>：AI 会把即将被遗忘的对话中“值得记住”的部分提取出来，写入 <code>memory/YYYY-MM-DD.md</code>。</li><li><strong>清空缓存</strong>：确认写入磁盘后，系统才安全地清理内存中的对话历史。<ul><li><em>注：这由 <code>agents.defaults.compaction.memoryFlush</code> 控制。</em></li></ul></li></ol><p>为了让记忆搜索更聪明，OpenClaw 支持为这些 Markdown 文件建立向量索引：</p><ul><li><strong>支持多种后端</strong>：你可以使用 OpenAI, Gemini, Ollama 或本地 GGUF 模型来生成嵌入（Embeddings）。</li><li><strong>混合搜索</strong>：结合了关键词匹配（BM25）和语义匹配，确保 AI 能快速找回信息。</li></ul><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>会话 (Session)</strong></th><th><strong>记忆 (Memory)</strong></th></tr></thead><tbody><tr><td><strong>物理形态</strong></td><td><code>sessions/&lt;id&gt;.json</code> (全量原始数据)</td><td><code>workspace/memory/*.md</code> (Markdown 文本)</td></tr><tr><td><strong>生命周期</strong></td><td>随对话增长。过长时会被“压缩”或“修剪”。</td><td>永久存储。除非你手动删除文件。</td></tr><tr><td><strong>AI 的感知</strong></td><td>每一轮对话都在“重读”历史记录。</td><td>AI 需要通过“搜索”或“回溯”才能想起。</td></tr><tr><td><strong>存储内容</strong></td><td>所有的废话、工具报错、原始 JSON。</td><td>只有事实、结论、偏好和重要摘要。</td></tr><tr><td><strong>Token 消耗</strong></td><td><strong>高</strong>。历史越长，每一泡对话越贵。</td><td><strong>低</strong>。只在需要时检索相关的片段注入。</td></tr></tbody></table></div><p><strong>Compaction（压缩/精简）</strong> 是解决“对话太长导致 AI 变傻或 Token 爆表”的核心技术。</p><p>当对话轮数过多，超出了大模型（LLM）的<strong>上下文窗口（Context Window）</strong>限制时，OpenClaw 会启动这个流程，将旧的对话“脱水”成精华摘要，从而为新对话腾出空间。</p><p><strong>自动化与手动触发</strong></p><ul><li><strong>自动压缩 (Auto-compaction)</strong>：<ul><li><strong>触发条件</strong>：当会话消耗的 Token 接近模型的上限（Context Window）时自动触发。</li><li><strong>表现</strong>：在 Verbose 模式下你会看到 <code>🧹 Auto-compaction complete</code>。</li></ul></li><li><strong>手动压缩</strong>：<ul><li>你可以直接在聊天框输入 <strong><code>/compact</code></strong>。</li><li>进阶用法：<strong><code>/compact 重点保留关于 Docker 部署的决策</code></strong>。这会指导 AI 在压缩时重点关注你指定的信息。</li></ul></li></ul><p>在<code>openclaw.json</code> (或 <code>config.toml</code>) 中，可以通过 <code>agents.defaults.compaction</code> 进行精细控制：</p><ul><li><strong><code>model</code></strong>：你可以指定一个专门用于压缩的模型。例如，平时用本地的 Ollama 聊天，但压缩时调用 <code>Claude 3.5 Sonnet</code> 以获得更高质量的摘要。</li><li><strong><code>identifierPolicy</code></strong>：<ul><li><code>strict</code>（默认）：强制要求 AI 在摘要中保留所有的 ID（如 Session ID、File ID），防止压缩后 AI 找不到文件。</li></ul></li><li><strong><code>memoryFlush</code></strong>：<ul><li>在压缩发生前的瞬间，系统会触发一次“记忆冲刷”，提醒 AI 将重要事实写入 <code>memory/YYYY-MM-DD.md</code> 或 <code>MEMORY.md</code>。</li></ul></li></ul><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>Compaction (压缩)</strong></th><th><strong>Pruning (修剪)</strong></th></tr></thead><tbody><tr><td><strong>操作对象</strong></td><td>所有的对话文本</td><td>主要是<strong>工具执行结果</strong> (Tool Results)</td></tr><tr><td><strong>处理方式</strong></td><td>总结、概括</td><td>直接截断、丢弃冗余部分</td></tr><tr><td><strong>持久化</strong></td><td>写入磁盘 JSONL 文件</td><td>往往只在内存中处理，不一定持久化</td></tr><tr><td><strong>目的</strong></td><td>维持长期的上下文理解</td><td>防止单个巨大的工具输出（如读取 1MB 文件）撑爆上下文</td></tr></tbody></table></div><h1 id="多智能体"><a href="#多智能体" class="headerlink" title="多智能体"></a>多智能体</h1><p>在 OpenClaw 中，一个 Agent 被视为一个<strong>独立的“大脑”</strong>。</p><ul><li><strong>完全隔离</strong>：每个 Agent 拥有自己独立的配置目录（<code>agentDir</code>）、系统提示词（System Prompt）、工作空间、会话记录和授权信息。</li><li><strong>多身份运行</strong>：一个运行中的 Gateway（网关）可以同时挂载多个 Agent，它们可以并排运行，互不干扰。</li></ul><p>单智能体在复杂场景下的局限性：</p><ul><li><strong>防止“提示词意大利面”</strong>：当任务太复杂时，单智能体的提示词会变得臃肿且难以维护。多智能体允许你将逻辑拆分成更短、更聚焦的模块。</li><li><strong>分布式记忆</strong>：每个子 Agent 只维护自己负责领域的上下文，降低了长文本带来的成本和幻觉。</li><li><strong>并行处理</strong>：多个 Agent 可以同时执行不相关的子任务，提高效率。</li></ul><p>OpenClaw 提供了三种典型的协作架构，开发者可以通过 YAML 文件进行声明式配置：</p><ul><li><strong>编排模式 (Orchestrator Pattern)</strong>： 有一个“主智能体”作为入口，它接收用户请求，分析任务，然后分派给下属的“子智能体”去执行，最后汇总结果返回。</li><li><strong>点对点模式 (Peer-to-Peer Pattern)</strong>： Agent 之间是平等的，它们可以根据任务需要互相通信和移交任务，没有中心化的控制者。</li><li><strong>层级模式 (Hierarchical Pattern)</strong>： 类似于公司架构，有多个层级的管理。例如：总监 Agent 下面有组长 Agent，组长再带执行 Agent。</li></ul><p>Gateway 如何处理输入：</p><ul><li><strong>路由绑定</strong>：可以将不同的通讯渠道（比如两个不同的 WhatsApp 号码或 Telegram 机器人）绑定到不同的 Agent 上。</li><li><strong>身份隔离</strong>：即使用同一个网关，不同的用户可以通过不同的身份标识访问不同的 Agent 实例，确保隐私和数据不混淆。</li></ul><h1 id="重试策略"><a href="#重试策略" class="headerlink" title="重试策略"></a>重试策略</h1><p>OpenClaw 的重试逻辑遵循三个原则：</p><ul><li><strong>粒度适中</strong>：重试针对单个 HTTP 请求（如发一条消息），而不是整个复杂的任务流。</li><li><strong>保证顺序</strong>：只重试当前失败的步骤，确保消息队列的顺序不乱。</li><li><strong>避免副作用</strong>：尽量避免在重试时重复执行可能导致双重扣费或重复发帖的非幂等操作</li></ul><p>OpenClaw 通常执行以下默认行为：</p><ul><li><strong>尝试次数</strong>：默认 3 次。</li><li><strong>最大延迟上限</strong>：30,000 毫秒（30秒）。</li><li><strong>抖动 (Jitter)</strong>：0.1 (10%)。通过加入随机随机延迟，防止多个 Agent 同时重试导致“惊群效应”打垮服务器。</li></ul><p>当模型由于“上下文窗口溢出”（Context Overflow）而报错时，OpenClaw 会：</p><ol><li>自动触发 <strong>Compaction（压缩）</strong> 总结之前的对话。</li><li>清理空间后，<strong>自动重新提交</strong> 原始请求，而无需用户手动重试。</li></ol><h1 id="Claw实战"><a href="#Claw实战" class="headerlink" title="Claw实战"></a>Claw实战</h1><p>由于OpenClaw需求内存还是大一些，这里使用<a href="https://github.com/zeroclaw-labs/zeroclaw">zeroclaw</a>,使用Rust编写，更轻量。</p><p>使用docker安装</p><figure class="highlight sh"><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">git <span class="built_in">clone</span> https://github.com/zeroclaw-labs/zeroclaw.git</span><br><span class="line"><span class="built_in">cd</span> zeroclaw</span><br></pre></td></tr></table></figure><p>修改<code>docker-compose.yaml</code>文件，修改</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">zeroclaw:</span></span><br><span class="line">    <span class="comment"># 将这行：</span></span><br><span class="line">    <span class="comment"># image: ghcr.io/zeroclaw-labs/zeroclaw:latest</span></span><br><span class="line">    <span class="comment"># 更改为：</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">ghcr.io/zeroclaw-labs/zeroclaw:debian</span></span><br></pre></td></tr></table></figure><p>同时增加环境变量启动IP地址</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">ZEROCLAW_GATEWAY_HOST=0.0.0.0</span></span><br></pre></td></tr></table></figure><p>然后启动,并通过<code>docker exec</code>或<code>docker attach</code>执行命令。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><p>可以考虑创建QQ和飞书通道。</p><h2 id="结合QQ与Skills发送当天新闻"><a href="#结合QQ与Skills发送当天新闻" class="headerlink" title="结合QQ与Skills发送当天新闻"></a>结合QQ与Skills发送当天新闻</h2><p>通过cron可以设置定时任务,同时结合skills使得模型能复用流程、经验以及使用function calling获取数据。</p><p>开源skiils:<a href="https://skills.sh/">The Agent Skills Directory</a></p><p><a href="https://clawhub.ai/skills">ClawHub</a></p><p>这里主要使用到<a href="https://clawhub.ai/steipete/summarize">Summarize</a>和<a href="https://clawhub.ai/joargp/news-summary">News Summary — ClawHub</a></p><p>然后设置cron,可以通过命令,但还是推荐直接与AI对话创建</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">zeroclaw cron add</span><br></pre></td></tr></table></figure><figure class="highlight nginx"><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"><span class="attribute">zeroclaw</span> agent</span><br><span class="line">增加一个每天早上<span class="number">9</span>点半推送新闻给QQ的cron任务</span><br></pre></td></tr></table></figure><p>这样模型会调用提供给它的cron增加任务命令，而这些命令反而没有暴露给用户。</p><p>此外还可以找到很多新奇的skill,比如让模型自我思考、进化，通过skill查找或生成skill,以及视频网站热门监控<a href="https://clawhub.ai/jacobzwj/bilibili-hot-monitor">B站 (bilibili) 热门视频监控 — ClawHub</a>。 这不禁让人们联想到软件开发中的扩展，从原本的用户上传代码插件再到直接上传一个md文件搭配一些脚本，例如增加一个skill，爬取新闻网站，也许这就是未来吧。</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://docs.openclaw.ai/zh-CN">OpenClaw - OpenClaw</a></li><li><a href="https://docs.langchain.com/oss/python/langchain/overview">LangChain overview - Docs by LangChain</a></li><li><a href="https://github.com/zeroclaw-labs/zeroclaw/wiki/13.1-docker-deployment">Docker Deployment · zeroclaw-labs/zeroclaw Wiki</a></li><li><a href="https://www.cnblogs.com/peacemaple/p/19620712">用 ZeroClaw 结合 SNS 打造每日新闻邮件推送助手 - zhaojie10 - 博客园</a></li><li><a href="https://www.ruanyifeng.com/blog/2026/03/weekly-issue-387.html">科技爱好者周刊（第 387 期）：你是领先的 - 阮一峰的网络日志</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/03/24/OpenClaw%E5%AD%A6%E4%B9%A0/</id>
    <link href="https://www.sekyoro.top/2026/03/24/OpenClaw%E5%AD%A6%E4%B9%A0/"/>
    <published>2026-03-24T05:49:47.000Z</published>
    <summary>
      <![CDATA[<p>最近OpenClaw(原moltbot)实在是大火了一把，有如当时DeepSeek的国内热潮。但是DeepSeek模型的训练、微调包括前端的数据集收集和清理等都不是一般人能负担的，而OpenClaw不仅代码开源，在Agent开发的工程技术上也提供了一些实践，由此引入了一波”养虾潮”。</p>]]>
    </summary>
    <title>OpenClaw学习与实战</title>
    <updated>2026-03-27T13:32:11.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="抱歉，您输入的密码错误，请检查后重新输入。" data-whm="抱歉, 当前文章不能被校验, 不过您还是可以看看解密后的内容。">  <script id="hbeData" type="hbeData" data-hmacdigest="4e8936eae36ead77711522a2fe9bb008b9e4a9e75dc8aa6e6b8eb4699c2707c3">b8c934a8d8cbadfbda0d792160af5b4530f95f27f575f8890760d3b4aaa38d388fd034e8dc3c866909026ae27d362c21177324f5a16fd5fece1c0ecab32daca8aa2bf610d62a15de973d0138db8f2f10721fabc8f29d61bdbb77db3e346801946f9e38e1e828d8079e56a488314358da6fd2bb76d3f58d20fe4c9b062cdecfed78f0da9765a982872051cac56a5f53ad362396a6a58beae5c8c6c168d42d8d3d844fb6f7e0778d64a24e57fe9caccfb8e31d5895dc5fd2f6850f3acd815a348ce2a8e9800998a54c4719eeb786e715f56fa8a0cfa3006c411e50f68c9ab733339c33973d593142f54cdf52f9312c290c8bf68a1c09c5d4145e532890d4f1c909bb341ff08cca3bb3be311554db1fe2dae036f1f38d34093bb94526f9e9a0287838e6f0dd6e084c82615aff0c6060325241f6e7af3c9ac4a38ad82003939f160d0a286559573baa785e87d67e31af71078ac171def5c49d0d0e8d70f23c4e227e426ca84f377aa30ce5a5b5bc2a6fc036e75109f3f46d430b62bd659159f06dc1a1e42df9ab1ff019edd995af7850790e2d3b871e8497df657559d18d4cbc8075f350223d0010cf16bac9ae00a6bcf73f828d06cae6904299bf02d9f18c6544e7873fad3cc83da66bcfa9fa2bc9b3d259f29e434098f8c4a6ea2c7ddc8dd4f4e8ef001632b0eaa68408a2ce3a377f26c86ffb8c7555cb9cb7fd517f955627f7d6563de39fc5f87a6a6fb78ef80f6451d109e6a3c7ef9142942e659164fe0f1fd7b6cd3ac5f38b4386e8dfcb2d2b9b1c83c12b3d9e672d2abc6f1323d0e1c59fb9bed9bda367f3e21745d5f7a2aecd604e5483cca3e5899d8044f6a16ce762ef986673504018a00f542c8cd445b85af1f5fe22a0b38e8457f4142c166523931854e25c78f4af7c01a781baccd8a0522c8b684c6939d57f0faa8f65334f3b4270dd9b3937cef8f1d7d97eb8fa7e5de5ff9c28f18bb804cc3ccbbe6810b28ae2996b6a04bb267a9f7878ccd28790a3204ff134a629b1d673a05831213e3fbafa2d9f70e9d9c4378e118aacdb0c6723bc31f141b37b075ecf6b1057501b73e7747764a8a5e81dda077cce25b2cb644f1a1644f5126c74a353307081be0ea35a98157c10b085ec3effce132ec73d95febbcd024a32c116f20bfe8cec328941dcf85dd86d883fa53dc5f150b0c092930dc2b56427ba692cf8340d29ff99e274a2f424dccefdec9f950a7c07bbd3643c1e815931c83fef85f7288e1688bc18538e99357d497ac71a87fbea5d6052a3dfb10d9abb57f037293a6e9cf21d9f27917954e420d35dbf4833ca198cdf5127cfbcc9d5ffcbb23ab2744339336c15147183e368608e5946cac9cb4ac2341b33e4255703c545735e3ce7146d566cd64422285afdcb7fe3b4da9f6abd322f20d521a5e724227c5e6a9b28291df66e8f3d6d591a8eb2d5d3b79608f6c107b44c13bb48068f0be7d013e57fd0c9db3e562eb3068fbb441300e99ec643d7ab2630181c917951f4280307ba2f974a4be3a620512a66dfec0c3e72aa3bf3078ea4d2f9415191c2c5e83b95a9b7ee738520690672f3c7219506f843456406892cf0a69ba0c381eb86cc519d237eb9a833574bc2ba9a7f2ffeced5a83b9769262596eaaae5d4b8dca520862d51cc7e78cd8e6d6ba0731222c03c4eb1a279d1ea08568e48286389f9723b18876fcd1ea82877b74c0c1ce2ea8c5e4b3b6ebee50c661a85913c86832755322cb435f0b8b30a04c942ee76888d3c5871aba2c2d2906db0c133a5edb0d2d4810e810c00eaae39edee820e735ef05ab5696bb5b1ac04fd60e7767b2ef76a5e875658ff1037b7b0f0c910ba31e455c1b51955c438015f32e23d7849c876c7f7b423d4940e02effee39934765c54b59a947e034da3b46df156ee4598cd6822809aa133fc736e3c909bb8b60543107e789bf3ecb6b9062892505b59cb4ab6a3293503c747d280b4eb0dd83e75ba3954d46e04caa2ddede550f9f9c8d9d68ce2be005fa906326d020393a0ef09a469d6e7f09f0f27dbcfe8cbec1dc1443ee9306d20e9073dbf0c70560902aa1c1c9e97e1a435e273e750b31487d2396a7734910fcabcdcd0ef560190f1a4050998572aecaf0af402d40a7f083f0f3b0fef6b19e1ed28aca0d0a3710f512c87145f1b48b7b5d844c55c81c53a3db965e4453a8d46f93bd9a563f848ca133fbce4f0f010196859e612cf6749b7bce2b4a9e5ce46765c1bac9663d67ccfa9c673cfde0686b3013b3b582d6762f053dc79489db242ecd3e287f496d7f3691375006a01056be44bdab7549f67e7002bd4632ff0391c1d096285898840c701cf94361858b9d77c1680ce1b347b15a17956fa176b72456e43a3f50fe86bb6a9ef4a35620e48258aa4d80e653e81c3474e1214b4adf2c8f40d5e16ff1096f23e3ea6df23e99802c468958a8ce9b63771c65d3e2b1e042cacbcf97d59fd0825241fcb7ffd5a1c58057c41fa33725cb540556ba9c342a59985c4d692b418f728f69bb4a54646db5133a305218566b9d5c7c543019de4c5c708428a1b953f1826ff12f81860f1cbafeaa6cf94cb9cc2cdb9d6b053567228edda59d6c972ac4e7ead617eaa728b6437ea32d821cecc5d1caab500322040f4f03fa7b07716229084bafd7d36f5e963d098202eb074d24838afea752997a0a406210a6385e085d1df5c28aeace493bd482f62c9a88a87d95a08b530346566b534017d48f0aa59b3b7aca0542c8d433f38d5046c18e05210dafa12cc304bafac7d8b00acb2609b73bf19368b397f1ebcf2c39edaa1aec2f6ab7dc25ea45da5e1e32c3e39b526d55e7025473e3f003421d9169d7727b9a92726ac44903609d49434eeb0421e21420095089cd296518d0f30c1a90bfaa1c270dfa7733566d95b823853fa2c3650b1eb7fef51e564fe578264d5f63b706c9dce997c29f2ecd2b477369bfd3968502b73cccb5db40bcc0be8aca437da21f7fb573079a6cd08a9d5b44aa37096c73a5a4572196cfae54935bbdcb7924e89291ed6bafbac22c886dc79a89d22dd2d6ece3cab9050e08f6ecdc16fe4c498a25f41d56a7473a2f5997637d8da8c18d1b7075d10aef592fecd40fb582781782af6669672e54384a5bd7aaf85b7549d3c0f6b2ae8958f09e1f02ed6329a22bf02d354debab79fbb14597d2a64a8b8ff6a5a4c51793f76342d000ce26c296687974634b74b5a3b594731cb9afad27cd12ebec86c7e70d70ac4c590b7acc359b436bb235f18748897943b747c280880c441efa143807c4f60c6f5b1a2fc993539dda29c401846d45304b82285af05a0f0ff449b0bca1d08228d87f349f326b5097ac0d2695268706c95291472c39df0fa06c556cbe464d8b964408cb01ef1a2bee88f0f8de1cb5193fd0817a921cda176ab51db193a1b90171dcd1b753e7873d2e7ca5f2bbc8e99fe29a283091dfdbf437462e2f9e5c3934b0247f3a18e3f553a54345e0ffe7525acc889e6bc825ac515442380c4c6290e93d570155efb1fa9446beefe2db60d2fddf01fc9ddcc61617f971993cde2f4272811f5c89aa99cb3d138d6ce4f9fbf292d4ab3df17823ae189e67b11b94c4a4b184f484eeffd8099acb8cde059675b053eb25087456be1c1d3b79a5a679a6e0d29af674fef92f440d26bff1302783c073400edeef150ad4494f91e2da601f857909aac3dc98a5df82f0787ece8fe150fcfc25393d2f35177ce59a5ca687765b908cc3570a22323a60f58ffda7acd0392172067f7efcac86c1d2f377ab779a5dc77121baf60035078a227297a34c5c1d3f4bfe283e12138cd15e5428b8e868a83a1d043fc1e2979f18082a734ef4bad425b5516badbbfcc5473acc0ba1d69b00becf0f33c3c8b9c6c95f618d398bc4bd015826b19e57ec5c029ec8a2d64b841cf6ea465acd0e2d57cd9807ef0eb07d381184cf93e7690b70cbb59452bfa18c62bdcca9fe324ba607bc702d56791d870420d817f1bd2163fbc3ea498a74e033599523d2cbc4bc60d37f5559b343764a8661495220034b7aecebd9ddb79e014356cc452826eabb967d11b141d099008fcddfb4d2134fd4298e3ca692ad32e1cb953e36cb5e4e5afd0f33efd8a086e0a8d88076bf7d95117990419446662806bc162ad63109c2b5216740d3a95e4533035adbcc1442c6f778f5373d6c8a42858f29b4d5b6813e614e86559ceec9ddbec3d8789a73cdd2e1938794ebe9e8925d33be295e122aa9bf1383864880e6eddf7f5c5fa7617e12ab362fe794b91b401093338941f545417ff4f5f420398ff1ceed5b0b13974e7b601b71e088162b145b88086c4b565ad8e044b7143e2f89f87cf54d9fabaeadb03abf0228d1e42a542ee9bd306ec569805c05b010e062f0cfb5f457746704fd4a0be621107506bb80cbd949faf06ed0d3c3197917c8bbd45f9fdedb8b63b93533d11b4933165fd663c0569a6c678ef306f3d2f077736cb29c113c6615bdd471f1d48803321ebba865d8aabaa28dad586a03eee71928b815757451b99c621b1fe48ef78e2a5d37192f014920ad56e14c0af35cd22c710b75aa95aa91e821303c6c70c81e46bdc2dccb246674b310d11e4db65b131b7ed774d682ba9616a3c6f64957a5baf6898842402fc43dc48f4995c5e1ad9d496d4db72bb9d24f1a43b51b5b16a59f471d06d545731e6b3ab045f9a84c49ffc8e82e3ff37b3e9bbb5cca41475555a859c0f72c1b9fc3a92e8c34d0665b5727a0ed5524a94c8a4b5bb21a85d363518a4c9d3d8fead06e4d8821d05ae5288a1fa64946f8c4bf55fbe07c494ee188ebf016281d11df68b3f0ce2ac79557b5db5ccd65cb9db8cf0af2ecc9b677628261ab5eefc11aab66196c4c35a4f9cfee0268bfd420c860fb34a5ab1beb10aef28112d51031f4daefc3cc5378e05df9d083d2e0a940c77c27b1864ecacc8315d2d1921a62ee1035fc1f4f34406cba43a2c1a86c74ef744df1bda8d6cb96c5d580fb53d7ebd37a1a3f39fd2207bc7a753aa2c654b937f17f022345cd3480653905199b0df1bc5297467be2601b2d2657dcb8a00543f14b6678472f6c56352e50a429d21abe4eebf896e7a6e240fdf9ac916a0971ddd6b57eed6e7d8c42ab994fcaa05a2c88fa6e5930a6cc8f2224a2e084bf37a881030342504a0447dd1ff92c1dbab370c6e377bfd8de7b4b1e472193a9d9574fb574f607343c2023e11b5a23e9798d469b83d0bd23b77a88d8165cb8a1e46f7734003f1dd08091822bd2dcf6eb576f3a53afa55c9327d55cffb8977451fc8b58a2970b52445c9f912ff175015ed2e8b6347bb1a0cdaa52da435d05c660a5e6e195f289ec25f226ad0d214e42a1957337f43a5b5142a7b04a590720622cd5929f068046e3962cc214b646eb5e2f8cc21fcbc5cfa883e10cba0db044ef9609707a4008e46fad2c046487b75ec3d59539625f73a5757617693d52f1c8e138629f1dbac350c6f55e2bf612e332154e74661a95667aebdeed5ab03ce7f5dc9dc6c4e23d3205c49f67d397a93ff22b0c7b524a0284bece96264ae3140247d4a3bfaa240628d0a772eaa86c23f7446b841c5276b8e70790aaf042877477d51fd7e9c46b2da36d52034ee46fc6e1a22a633d8d48d73614193c8dda83bbf06a9b47da549956d170cfaedf6c0db07a189105eedbdcec289010c9f2d55f4c09b7f011716b863c88af63eb3724b6913a2178eaa12b674b9271f6277ecd404ad0bcc37cc778b8685192ae7c178b3d460f3571848c5daeba4a5f910e4fc1eb8eeaf90f469414ad27d3e1acbe4d54d5ffff651bbdac946f6b34eaf971891b0c29bd43946a9b01082c6b28010e8494d0beaf240cb9cbf2d54d3a904af55a2de144f4430ce6ad8c971a050f555f4b4f1b649dfe9310f029f27c18a4fd120051cd1161a191ab04a1592facdff1acd8adeadfaa9997325b57264bbd8a5362061d63b8ae05d43926767475a31b8427496567dd0645a7e9713af4f28cd475ada9b78c4e5fd58386ff5d61d53cef201d849844035d09b670805bb89d5049e710c71ece3c41a6b5db77125fc13aa2ca992a94263fcf81cf557fc3177151a6c6cfede3c8aa1bd1830b054b8ea34953210c43eb8796b1e516e2a32363d8de9e4222cc0fac6f97d79a8662bbf626471f2e3d09393d8eedcdfebdce16174e39736e6454167408d03e17d426f4e7ec33765a4986d1dbb2ed14bc0e4c4fb50343bda2c5fd904aa0837d3826be6fecbec5fe9b7640d7dfefca84e11986c15ae56634d6648d944084b491931b5524fbf62875da1a956be682e40a79312b8ecfb8739ba5e12b5034d8034f141891012b37216fa9eebf32218a97876ab2f5e77967cf1cd3c7344a3e21a75f226acad23d56609a6940156274dd9b8c0b2a2e8d68572bf32a4d2b29ff2bd7912a41339be227f7a7d3a99c373615d9d71744f0d05d62e6284e8448db0ba08bca234ecb29f274ec410f470e665e766b3e23bfd930db0f861c64db6f4b17dd247a7b002ead9c8015e16e7e16827f375e221594f7accc6d2259b72128060d1b717576a18fa182c10879775fc3299aacec0a5620863f11c66341ec9a09b620648aa462a87a64cb4cad6a5aed2c98bc34a5e3a81e83e54eff5095e41cd202afa81c0bc46f3c8d0e98860e2492c5f1e261d31a0a60bc92adc4ec12b55a099f9b7aade0ed886b0cbd87564ba01c7334f783fe98199188dd51afdb986d3d54a7ce94d0594a4e77e59813a8c84d561b7254a5f78bd7c7eb08d0217772a7830243ffdb578ceabeda4ce6eba0c45a53dfd40a1103b0c871a2fdef56183d0b975b5f1a651ed30be57907b1e5b1d3d465bf6327d812bc396884caf479f14479f43a3ae6320645ed988b18a2b3778cffc783a1360d08ef6ca376e6d79d5abf9060bc6fc7eb0a1c6de3385c4fbd0fa19786b5cc48bfbc291dab456bb5584e96a181d95227b76b86f49a67ecb445337aa51f7d8ab585a979e8f567cca9e0ce8d3bb7b313bcf84683460e98f883c5c5fa3a6c3e93a0af830f15a743649ff1c8e5fe2489ea0e67f4bfcc98b51719abc0cc4d9abde79dca26e06808b103818dc67d870d670058e553e4a0176339c8774829b1a65a4a1a748d15c8393794c3a185c488815f842a74a9af7eda38817245c7eb723fc7209c06fce82e7bd08166b64ae37ee983c3a5198395de35cc27e4d147e3c7639bdedf46ef4a744006421cee264cff61e79cf6b7e899bd9117cfc9e97ff75bb18f79de4b22b505ded5c93a9ef52508a4f086830b4d2093a3c08d29b5561262ceb8181c52d4665b5f8e2d60b13a8dc4e032a964bb1568353be0e8c91d30a559f64398e9ddb34ffde4473dbd08a491820ccecc8f4b928c9a0f8c1b985227f859daf03d8b2ebbf8997693057787149b392b23bac601407878f5c4ccae5d373c97f9cd188bd3f413e50f453c63df4f9d6822629d001edd808d340a511821cad9d20673ab9e5749918f2a3e6d7c8433e4e6def060f1252fa9df238c58b18b5caa8c9cbb210003567c93f4df3196ed3ef525caf29bf579484e6208207d6fd3ffe0596a410539e81f6032ef28ab6b281c4370346a050f983974a080677d07133774922f9f13d7dc4f51240db53d78b5f8f3d75c61436b0a6bc9f23d36df2d0de850fad18e25ec04c4b0b8dd837dd277f93ed437537276d4f72652355761fd85c28a945b67258e7015234045b2c66dfd42aa48f2ada720e18889fa698c95b827fa8c0b888ad8911868d77f72352b05a0eef721738ca49167bea74d4ba9399d4ef7ee8fde09627b821130a6e9ee577e897959429bddbb8f8ee39e14a33a5d438836ef7059b9dc4ad77a0d09e46b91d4c1ea81107fde02ee168634f35048a5fe76671ac82ffc875d4c3e3b6273471ae488367fec8c10a19b8597e5721c987d67efc3f4928cbc2dcb60b725e8ccef12cba709ae2808a7ffef4155555a9b8e68f54e7d6f0bb93c321c54d1859fef8f452d9dd2c57854e6a8a60ab496bc6943e51079f48fca6d146bba5127e5915c667e9eb76c6ec4301a1a0e79372d87a0b7299451387bdac141045081382f9bff9b234f5ef3121930877cfc6b5dd40505814f4e04ff9d613a6c64cc570ebb59dd29403fdd26e2c93a84ee2b3fee1d14b9f7b51c4cbeb9fa75dd2735f83c6a0767eb4be61dcb0dae60883d2ec9d1392b593c6100c3d60d6f085518d2bf316e4355f697d733229ab7e0a14557e4792ddd0225ee4d29ba4b5239b129e4fbb141089a688b70b6527d25c39d025e3fe178d8fde7474eae11d72eec6d76fe3ea0470877eb4f81bdf8cb67ae0b4a028cacced9b9fd28dcb7d3324a1a489ade0c0ea5a3d5bc6942ae2903595c2e527ef37b6628641eed8a3f2e362b9fdd2a5edd927bb484b1d43655c7df8479d4ca9a4df039b21a12faabf28787e252e0f09d48d97af6cae04594b27306c64816db755b852037a92c0a04e5015beab3457138ba2b17df14dc80cf6c70f290caa96e41b8d1118508d16718dcc0984339c439d053ec69a8cdc73ae5d0b1f8594fc8e9eaedc9a22531494e314111cdbe0e182d36f24c34f96c22d997a9a090267c26a2c523faed77465dd64d64a6f386dcb09a59f8f060d363f04bf62f740d58f5c848c104b7767979ff9cf80bbd72a55a2b47a91313efbc745ad09f2832675d5159058bd6b4e9bf1cf45352d74f6cbdf5f2cec7ce0500ade5b8b0ae796c6a67c5197f598f2d01c92aac40d2f0b38c372b3d558cf9bfdcb5ab4c5bac9b6f7ca14a7a80c66958d69f0ce2d28217f4d8001c8f9e0621f4e6c87d37b938d51627ff9299d1c388513bcf339c2a7abe758b193fa81e3897bb06bfc54dc95ba4c4db6eb56200526bada7503c2ca0bd11fff691451f0b47999bac5aae801127a004f0dce8eea5d1d11141e809b8616287aeace4dce1e05b1294e34f38888a57527a574307889cc509bf9af702f1597c5bbf472c46acce319d1034bdd000b9b57915cb8a6689fec3f20fb66c168bc90324f168a39e08e6b893b5aa96611cf96af1f360310d4b8572204b5052f9c9765295bae260295f5d26b3d8991c986a806686ecf31a7f03aa8c1ecd94079cd22a7e91771e59c624f5dd84d015e84d76d0a6fd4fd1c0e3bb4f2b1b84abee03e7542f875962f2093df1b3c0b283cae28954b99ba06dc9f6e0de811a10b136db055d0565612e48a1651ae768b7caf43c0ed5ca5c6c8c9ef28d8e796db3fa8198c4ff486065ea301390f392f56ea288aa2fea629de7be69498eb6f0440b2aa7e86afc9f9578d56541deca4d6dee58a0c006eec4c482be2a3b6e7bf16e8d861cea061d09c646e224111e82424847d1bdb197fa66b0cfd9399afd66c2bb6a0b4cc067f7fdbdff87f2dee17267e51c9c5e74b0da13a6a0227c8fe60c3224f8143b48bdf07dc9e9b7f3368b75ccdaf3f9aa578409c617c5d69ed0102596c8de6fc430e7391dc3c429a357c55e2ddce362960cd9b75c172997eb8c6e56879a2a606d99bd75fdd633379189328da2a085d1f14d55163c9ba87ac12927151cf24c5a54acef1961aed0e962dd6205938d398f31807a252b6f1e44e13934ace3e2f33ef2300fa4d2ab64e3560765413ec3c7e2a5ed4c7f0b84290d4e69b2de95e551a2e2b1f78873d005e5c128b54a09ee213e0c3035ebaa5bd1e807e60bc9b64780cdc32793e70641b4e42c16b33d291629abce92a5ff0d86f9a421136aec8a184751a0cb8b4598ce3cbdff3708846c6e305b482f20570eb293a5cb2f3c5290171ca15de68c183a40fc481f9735ef77357b2cec6b65e5f60749532e1b95fe32f229c1a3758556e0a9918c9ded4eb99e353e17eeb0ce297b491b13e1d6422de1dd22ff4ccf736644ce4d6c9620a8ec01d71145a9e8f42118d0487a0b0042ae2a7e841046df5bb8967b1adcb7092ebc6447966b1e587325f382ff32862d03c12610b04bd546e83c5bde899372695134cc05daf7e63aa8840eddd1919a244771df3dfa814465ccca47ecc7ef2ba5b00964170cf8eba9b91566a060b3854f7e32247a8b6c1cb9e3caade7d736ad8eb57ad9cdaabc6c18d73cf66b038e6b1ed85695f375b83828c63ceba48cbbddb2866f4ee3113c23f9671d2a5dcf38c35b3b088c198b1d3f10df1a28ff2ee320d1e450cb7d4ddad9e7ed25b5e0ea746b553d9c22afd8842f996666ab1b06c5434d618c2143cdfef9ebf72a8be6f11f14396a6075effeb2dae6b3361af346d8503afd45baaa5232ab84c48ed0f0dee0424afe1ee7e19d0b967ab7f98e752950184739fd733237519327fce5058fd32ceddffc3a21d326ff27d7d4e51f2877caf19b455f314004cf1ee3c4896e5fd856b08a286172e75256b09670e51039a4d6a44f16b9553ff82ec53a94b09e777291ebdc3531e0382b757e203b0e05ee3988a3bc640ea41cfff768fdb85561c24e42abca7cad067e08037cd925831151b806a1a1ce6f752af5656d4c177b4f619b72dca97b52c937512fb57bd159c67bceb2901491bc2232e75b17be8220dc5ea783fa2d18c37f9040a263072ff2895e2d6208a06dbd9741184b965a403b7142acc90fd2c6a6a2ed126e68fccef243442a4865b4a55ab94f5df32aee1d9e27f6f591e99e7f754c3e91441a2bd94cf2c6f7e907380e10767a6b26db4acdd02558e5cab10730fcdbbc61fef934b4cd21181101d9c3fe3db64a8e75dd1fbf2f2cb36ac00b2c261a30764ac6def7c13acc8cf5ce60d89ba60921c227d86d47e6e4ff5f9824a1925600fb202930279676f1130157d044d196b073e41aaf50cf9979990bf99c3d75de980c7674388e5a8129f9dd23b0df0f8ab57529698d747ad0782c7488aeafdeeed5a350e70a101e707d6dbb4cf01636639adf94939b8658ea32d8dbd56bcd1d19587b445ff8ff88e4c3d7db54d6324cfa65758960c2ac2c7f4961b58e53711f3fd81395ee408237fb7d7a18c8590f278f7186660930d3b14b9406fc6e8fefd834da3e75002aa9d038a6c41d2071aa877ec92c4d1997030a734b13724c284c8e9b05171b0973224ce10ef21e9e726b8e19a2038b8f8208314b24530350cfc925e8650d8fb8f6c0a3d292861e17685d080e7d644f8037230f7d2b1c85cc9fa85b8ece76d594a3e338d2f6ab1f240691dc813c442c88e0efdeea923db9c0594205132a51a1ae05a204d32178979cac24ff49332e82ebfcd52dfe5cf05c1e7acf6f9f79fe6a91a1ee7d4b23b34ac067be04fae0935ab2e3beb93c89b63f983f6d7c10a04a691b5ddd39acda4209b5bac9e1489a49553bbc40f62f6fbc5b405d97d2cee1c3d2a7f3960dd7308136a8412d9e32888a48f4e987392f602c4d8ff5c30f4ea9743cbbac7f87e5b8787d9ca7fec67371faec16bc7bac670589dd0549288160fcb780814efd38844e265bda94d5eaa4668c61b7b98d7bed462dacf41f35edbd3f243d7ee460dc2cbff229d9f8bab1f4bed9ecfa21cc1587b64b709040dc74294f5a5459d466d001502d842d83cc485600132bae6980312b94592c53653e281b223d24035a8143c4b14e694da093b3e3b6761f658e2fdcce29ebcbf85f462a56bbe8f83608d8f7b05c13ce811229156eecab679f2aa3b3457faa002c2a674e1fa54f62c326b84b0b90791e1692375747c8703dda31b1f71a73929170a9a52701581f22b5f7392b33c344a2e5370f2e1c7df3f51d7385afe61db2f833287bbb4015bcc58d5c8ff1c816fc830295a87ee2efd9ddf4b876fcadeccde0fb99dd221b8a6050e787fc73892107757eb4c58a70e4541daa751bd03007ea00973be9aa4ee17c06bbdb599e8e175b00fb0df9339bc90e4b0a235d7f048896583917945fedb3c4fca0becbe4174cc81a2176c9231ede29fb7754ebd9ee2188aabdfb4738212a1dff87f62ca183c4b35249dc13f7ecd31528eea2c19407ce61d63de1ea9b0c517b82c5f3159291c32463aab1ef7d614215078d6875c9471d02fc281780f058780e4c1b8825aa2849e8c1c0e75da60633249af3a54c4c26e775ed369f85f8724e04a154993f56ce63cb3dac2e1d489e8d1ea01ba384ee0b1bd16a6cd1a3eabce264d5f50a8d95e8c6f3e81d67dbe11e1362d63475ad26f02435d90863b7146905c655526761055cd0febfce883e1a4550c7f13d41c8374431d1d334951ad95e54745707b9885fc4fa76c3106d815db2366fb81ace9b2f9b5f58e4c2dbcd827340c420539d8802d8526fc64dfbcdf84554d5fbd5d79bfce43fd8e85645af6ce3758b010994f97471e35d63ee6b7898e157ff0544b3bc1c118c7f53dcfdb32c99376795f11f8395a84ae99d07c458aeeda8e13e555be90efb714f21b435e25906d290e8f32d56be5defcd3d8471798fe22e10cc0b72a186a021524d4c0713e631deb4558d4664ab0c46aec858dc080b3956627033bbff80593e5cad043470d25f461ef883b706f06da655e660d3aca63a479da11160b9451f58ff4a4df1799f373022596838b54fb1ca0edec87693b8aac15af0c8b3d8869145588625d4272aadc5d95735264a202dd2392f6d9168420b05ce5e5c5639e500e4dc8214c18a5cd1a3b5c1c33e7b98f6b2082671a2643d6570815e0385f33fca783f929b5ed6684703438f0f3bcac43e198f3511e277748883ae1d54825aa48f1eae75eb744bc004470303c994a5cc11d36979160ee02970fa55c78891b8288c78aa717467913b41273605d89ac1d0ea8c71efe7d03a10c3078e485b6d9d22ca9bba169920dc984faa368274da7d590179b009586e3dd4678abc28fb38d4871976031477a200c66a824e5155180d7188aa44b7fc29e430a0c44259542c9ec2756d03e0928aedb40f1dcfc7f6a0ba11c5d9f68161e1ca6eeb06cc9d777f0e700cd5b53b656b5dbcc42d6ada9ddddbdd3c4832d5bac50df687a8acfd442384c0f620945bf345e2f3d4faba6d005bd2ab9b7c7c193479c39e3d2a3109ec6410be0a43f27014f9cfd88891093eac812bc95b88da5cff06104cb3f40eb7acc17e7b9f3266aa857c9eda25f1b14f27088e682e84aca1a87306257a3d251dc5668be40413b6390d67cb680762dab84faf7e9bb6121e6ee09243490eea32f1f6cdbf5481e7268996e33464a6ab57ef88f49fb5de9b914d467bbc539f926085b4b863ee3ab95c27873d8964be7101c546296dbcebdad8928c01e6513418ef6862ef0d95ee7c090d803e9c98a5a71d5a42da7718cee02a66b8830f65da8ee5abcf56c573d88272f2303f8d38f059e61d7b422789bdc799d2a5a0b6f6f86804bf09681338a0723700a06d629e2de67cfc0324ebc2ba07afbbb88e632eeae3c26e351348f3e20b20b08712a623040fab8a2102f0ff867f4a743af3f44b59939984944be0dd390cf77a35c7dabdc83144a0327261fd237da49ef0193195a02156369163d6befef33b1a032f86067e49062ee1a73266553813a015adc22417842b0d3306777ded856fbe01f36531b33d4eda0583af384183c3f1b8698244b032938368c2ddefc0d05329a18038e5e9e5e81b0412035f1f4ca1a17be47fe95584914fd783bfc86e6f3af37eb802712921a829e83bd530c90781a2324fd25fa8006152dbab871ccc76c3196e8148c914f08b4cb1e2b63d48bbd2f718f1224f12e6fc9f50a5b14f95be1aed40ada37bb644cfd2b12dec79b4df7396a7f117d1bdfff64c268e5dc28e95bcbd5701029a2a633560dc074d28a8914846e2033439a965651555514a18585e2f6b15b4e09ac9102fb5350b151c4af1854b030134a226c93bf799b532a37ca7ca3309f1b21aff4607362cac5cffb7b41e722f553c644de7215f363faa89ac4efb0c8e8b3736094ad3992309d70004ab93a3e29226860eafd3190206a0786022d2ba6baa24ba79730453caaf38897059189bf623bbfcb0108c671c93181b503ca5199ad0a6a8bf02f9dae59987db19cc7fba37b07c489c72089821dec9033f2b60c3b892000e06b21276a28072f95519e5f9376c7ad666c2720e0552018fbabb12a70a150aacad0f9bb595c0809a72c3464072ac31f0c94ac22840cd76d5682a0b48058554dc6e54cbcc87e7341f571e673a08bbf107d66d52917b09c58eb99311a8d4cc76759270dfc20c14b4730451a9e52612334160b087307f88894c4ac6abd02693f33a29ffb51c8ed70de402405564b8d71c1a84ea40675a9ce66db2f216f5b1b301fcd35e5d633d95e21fb72bdd4841bf56c3d683eaddd97e0ad6c194c2440f13bc00d8cf3f621cd148cd0387c84fce0f3ed190456b9715f1ed8886fd1dd90af1783df80dddaa681df2dbaf94fe3002e2d4f5894253cd6707adb9dde7bec347b357115c775421c3bb56145b4ffb18754b29f0ab2c3f6eb8a2f855c83a45058e0854a6127f26355c31e5ae93dc8b42107ad05cb7c41d577c47180542510d1d495b79283698ae88ed76b12be27c543cb061245b46edda8798ecdac23d9bced0c58cfedb7eb696aa1234d63af14a74c60523b6d6707a8a9e6e79488b9b524758b8888817880c076fa601920ae8a5aa74bd3e9e6b545ecffe60d3887e71d0acda7519a4983310515b111b9373de42e1227f11d53e8c08f028fde219b9a1321cf2b1c325ca60b8c9f7799d0470d4cf0e195e4cececa414dc0edd33f274a18405a21ba76cf45ba7332e05b13488cf9d875e364396014af2142386c7fb9333e44408aad07eaab03275eea6666e275fa7dd774391eb2ffe3d9412b932d2e06b53a02392822e6cebc3f686aa1917a02febaa845328e4a23591ba663e9d6d2b24439540de772e1a1336d4e6cce66cd2ec67fb9dfc51635b6fafd798ad32e819f893178dc047f5c24c0cdb8b1403a33eab46b6c9ebabdc026621187e94941770a289dd51fb5b423213784f4d212ba648f118b0fae9f65a1cc1899daaca59e32c6d25ee84d46b4aa40a569bf8e1e89e95e7a87f721094ad21c54965ca1a0353ff2b66e30e1f790179f4849e97256b6a7948c7facc0d392981bc87ddbcd3612c2e147410955d34de9c3d67ab4a429e7d599952e17a80f74b9a4602949cd687008c7a4bf70e48911f6bb52fbe33137c5cd9e370bf0f488f3ba60b9648be5aa57f24cf5bb66f79717ac182ecec2ac5d634aee6e56868389342d7bbe0b268a13817153fc9526b8e0a9f4a5c3f0943069739720b2f2e0ba141a8e38645c0a08897244f1be44ac14b2c3db2d78a096236c9ec2096b12bddcc0d9b77648b9acdc71a898d501ddfb9a27f9015be0355d02ad77132da6928cca12390b0816cf768bbff7c57a3d19a9af0000aa0c19d85de7e1694df6d5cc243f5624b35020c320e9862f09b6c4c6e2075cc353a76e48074934debc2a2b35e763e64385cb233edecbe9e691d9deae373e4a959ec928d81037a0dd142b6de51fa63dd298b009e96c0356495a30042f9c9a9bd5efc46e49b0610851484f7a5c6ee70b02221aeb41fe94970f8e9d44fb5841db25c571a76e7de98cfc51b2acd09889111347b3a93302c05cacd9c990dbd3322bf4d565bb39a55a846635ab31360a2605865472fa383d744563d3ce9c95032787d1d241322c73fd555d31a0406291296140d1cde4d58d14a6439fe0760341ddfdce5a435311fe47db679820045f1e3528d6264b56bf0a1ccb99eadb72ff501e5742e479f2611e36a35830ed64224114ddcf0c5dd2c52a74db02a486da5210882e0cf8d3cf8572345268484477c0359a2631661008e8950ebc768d51c7ad3a99adc38fb45ba65d72cbc226a3c44ba1edfd417b8ab74fd1d9a9cdf3fa04abc8527f278117df37e0700b891c1af79a5e18f2d593046da08d0f1d322d1208a5eaabc2dd436e9224f043f881882d3fdd80ebbdc1f1df192b1cdc9cbcd0fde4a1da3a8004fad9714aa78a3f64547090bf4eb3f737e4dc921351cf6320758aa50d945219c4ca605aa06e7cb7f19a83667a365709ba60170235cff1e16bb0144e90a6d8895e4a78898ad7661d1bdfda7a6bd2cc4885c3abed15c918aedcee6cab758da866ab5fdaa486b5dceebaba8112d2d321c724229f2cf39084fcdd94e4c46d6ebde117b23de23642826ad4058b063901a5aa4462a59491a8dc2ac7156a11d2675441d430fcf28d97c2d6a2e89714a9f3b29a8f1c33d654d61ebbf20ad88b7c973e4fd0d0cb8adb4fc5e3711b8d71f0906eae6bb5945b647509dbf1a9b9464e610eb449b366d2d3a66b800907fb464687bcbfc1920370d5768af1b1f2ebe7c102c7c0c0170f5f84a57af3054b5135ce4a88929ec1df09cd5c2428ebe0c713ade28a361a6e9416e13e98887470e6c0f0df7627eb3556c5eb8c56b1f5a0d1c77564c1a809ccba64b22dfd473240227ffe997fc115180ff08ff9838d9ef0ba53521d8590fe5df483888fd35aa8377a0abd75c5525701e6d118609bf3390a17e33ec56a0be9798e8362f403ce9075dceb10599d2087571dbc24380070538a506051a703b45e2226521752606b1a6db21bce7d7fcb8dfc927dd70f1fb4baccc4e64c20d0478321372d3ac6deb711bf832f4a0c9f3be8f63f0cba225375c93d9f6062e663dedf2938eac3a1fe45388d1e5cd4a571e034e94cdddce715fe94a2aa70a795b11f676befd65627e3f1f9dc9230d18ab7ed43288b1d6495b46a408a661fb57b3afc53cc58b3d5958274bf4eaeebdc9e523fecb3d40f1b198c9646ed23900ec692eadaed9af39660070b48d0e5b01d4d31d60a83262c50ca8d810c492f3d2257844e102ebc5edfe7f1282bf543ec3f0634dd1d84382732586045d2824d44888e983de1147c79ade8cc0b6b5c4d6a8eb85d199426da0e07a5e328618803071fdb7fdea9e8f1cd707c1ad631f4ce3cb3c1e885b16ffb2f7842ddc5ab69d0d9983309f24924133833d2c85a627d2d5556be0359860ce5b352ddfc7d4b29a0400d4256a929719a849a738d10d819a9995506fdb6b14db264aa4da7e590404bf201d818c0aca3f5546d580f03766fff56e1f59de2c0513923ec453092b9a62d560c2bec7b9cef3db691126f37f45df9fadeed7db422baed97aadf9f40413dd8d0b64d34bcdec2aa64cf1544e3edf2e20564e73e354a8ad4867effd449f5258f16cfca06a76ed11d48b9b1c69a5b7cc30fe48485db7609895c49e870808edeee07a9653c1e9351f5aa21dfd12463f2dbe7513dd19a5dc85eeffdcd4431d54946c66780dbfa55f2adb8c8311b5cb06bc28353e2c2f2d96e016d3b5a201874a15780104abf977c6261dfffcac4d7ed32ada1eff4bd57933ed2103ca399b022503764de3b30cf800aa1c45ed146f8fa656d69a2c2ac4abb39f190254a44df42381e79c2702e214dfc9bfb6787590d7ef46684c13df8b2a726e1afaab8007060778a0bf617af4a529e2e8549b9303e6f09f0c47238344a51550aae33f8f6dff6342964fec56d47af6428b4ce2482b8fae6c9fc717a559fdf57db67f232b090dd68d49b6ad827c95d6f92c27a919221d8dd4813c15931cbc4ab8636baef73c74f1196c72443b62548ffea1150de3b7c977ac9a5c6bc3b6aeaa0ce14e084c82fbe93cb184fb18aae6adb88f8da64784513ef806decf5eb5382872d5334348546276ae4ba67dc16ac58cef8676f5aabeeed3c57c2ed157525f8b90e70c932ce8f6781fd81b562a58317ec7466a4a065d5f423af619de91d2907890c7057f9078a8b88c9328fcd6aee140f67f7a163ee33ca1b45bbdd5df0aa6fa63344d1c963078398d9afaca9f8999c9638ce6ad9595333123527f561e88c9772023c89b9c2bba9e5f3565fd5df04d6bfdf65df65d20588086c291b9d2f51d9941059acc623d1fcac5c65f09e6f93d41aa69f8e2f4c7ba81972305728eabd10550d4524e63131f9bf3568467c8d08aa469feef0608a15c3c5cfb49587fb040634e793782c3502bb95011adeca35eb44bb9957e48e32910a78385cd24572333b2549984896ad52e55a68d25c588bf80d8e529aa91cb15cf791e8e11eb3b19c4744907fcad7bd07f78ea329aa12fe250b811b8322d1915b74fa6cc45d158bb99f3bd7bd282454d1bb81e7c5c5b64a449be44e6107cd4027834c97360669b8bcc17506f83407f7b4976942fac057b9e4b78c9d9fbdb442b8a6b948d7ed98c5586d380ec5486ce537f98491f8370f97c54cd455079374e839d43036542d54c70813beb180fd5f1a1931713a27e02f9f4713449c5c3cd7c869f343616a5cf29ee5650c6d02e692c8a343411fcdb253627ec19a51a383655d7804a0c3b2b0bb718152a48916b12c15b385dd097e67099405ff7b73faa9f408305fa1560ab72f046c9a14b4b3e144bdf25a5dcf90def13bffcb2c436234477cdfb3e230831326dcf2586a20e67f5c08e95c873f63287ef8af5c1b045f9b9935e860b563aab282c3ad8629bce4326c8f3589d8ebd8479fbd6f13a2dce4cf9c2419f7f11ecd8ee573461ca5f63322a70e091a392cd0bc201a33cc591bd4023c2d55ff1162a3588da249c10dfcafcdc0f44bc11ea29ee180c54d689fa02a29a6c73dc7951615898b95418bd04533e713a504c467eecc92340fee87a8ca4d4800070eb77b4d587a14eb42791cc60a5b857d5f325a5bec6abcb69ac6eab1e5b557c7b314839a9b7a6b4687bdcc8a21cca29819e568d65db3204d14e4286ad97bc480f18f1ac25bdde4a355e7350690e11fdffc14fe3f0ae0add2c2d7798833868fb06285de395cd81a095b0bd6106d9647e09cc6f1a5d47971f6724e73060684e2373449036cae29736e30420486f1663aa3263957b0589f3ef76b57bad1a4f14cb0cc9659597d2c8b11b28e35b16fed74ccb80cd9dad33cb59608dca08f3598657891d95152ab8cb22d79f168fe02adda7067085b79577aee3e127236b3bb6317b7a16dc8c5f201587f899be55973b3a054e015ba5516a8c17236de5b0b5ab61f034c02775a2c19cc2f912365e7dc5f7f57194eef09e39acfcfb9be79a154c797f785175f0ccaf2c0d6e690ef05cef1ecc71f3e79c37f235ded48c2f0d561d93ce3473d99eef9b193566fcfd8cc7dbb1813f234204f660a9bbc1ed3eaa1013a47a2406d60164b0dc1236dd57a613fa32e0bbce04998b4dad9ee8b72557e0024ef9516e67238b4c0e3841a200d8d583fca32c50009d678cf0601abdf399a602ce811eb7d1ee6d6004fe586ad59de9def592394afa65786bf42a07f96af32f4827c04b28e4421b9b69aed716a53cce13849974923e14e346f94825629f8fd19ff764b6a7e95b4ad710a332d9e3f2c58fc88d4e93b837a29dfdd4c0eaeaccc27fe5b567d87aa619e9e39d37e76dfba7717bffca1db4dff8d6a24e1dd43ce9b8ef34baf585a314351c311a4f43c9886c0bf0955dad3bc04078ecb9842933cce68413e1fc1932c1a267f4ba8c9bcd53bc9b8a1236951816ab968a8ecc24b64297fc4632062d5eef5986cc098a23b489d9d87f0d1a81ac060f4d678425a76209b5f15ed5a3db9fe622c8e1ec8d547a10b67abda07278e8cb57fd68150955990dfd7536903d6ec8a15c1da96c44982dd668261e10900fcd9a03e5815f68905379c49d860c9fb8c72144d0fbb9b610f6d225cd6572e0e122791c20bff51fbd7eaa720a03c1e834489afb2e726af19a4de48b0cb24422aa70a71739a9e7e818d900e3e1e788683fe16650b28eba5a7b52e7721b72330878915abefd8dc47ea3dd8087eba0a6a0c38ae9ffb54a5d385556133e20d62816edc94c48418306e68971c99e91970db6247333906ba40ef36bc7c071baef952d3a972157b439cbbeea9535720fbd8f6c4004bc011af2c2bdff6ae8c0190c6d606314a192dedc10590de2735d7db97bd49b20e6b45e160d0dd252dab9c8a6b5592fc47dec7f6768dc879cbac1f2881087a37fb36e64b0a84fb4d42a58cec1417cdfa992d7c50af16cc86e816f345fb790bfee0bdb8455ce86f0bfa4f7d6ee1e025ce1315430482a923fda8359ea47044740b4a1b60877ca977166bf0691511a5ce72406c576d9bebcf72da0c463f55f5b5b274efd79c419bea7ebe8aa7f5452f35a948df13479970293707898f414f2fc82b200ece37518a4ae8b6a28c835db8ffc4b7114770ed459290e0c02c7a7641242804b9a4e1058479a3c0b905a565baf5602579467a59d354984d63cd396f41fb974f925babecc8b46c6d978bbece187057bfa5a4fda62a47b8f8c2d8b286fd015dcbfc8856f36c2f5ce0e9d81c6b20e38e096940be18e701ea191dc9ecc83ba47e10ce6e7f00e4975f9b737b41f7d4d69e7870d7b25f4341006e13280d582e0944af4ec95884121bfc0be19c85bb9dbf67e86c36e6531fadfaa65114ab1162aa4af9ec569267e4762fcf1aad5331d8000145600765d25fffcab03327b753982994574218507ea97f3f7b5b380321a16e023d4166e86fd7cffc475c24c6ba72cd72caa53ee42fec801ad50ecbbefc3f4012b92f7024ff1920ef97f7069c48e00202aae52a55b6c2d7476b9a9636c53f21e8ba161cd0a6bfba4c5f5d63e191513d8f02d63b42436f9bd3b65e8357de34029393f5cd13b3c631d5ac8fc32718af0880615cb49940436c3a72d82afa9b614abdd17ffc1b1946193f086762387341c1f8a96e80c08b8059df8f0dcd9a2743674548215c835c5ccc6bbc47f81315140de720fc99c3f9e9d80e02ef7b1e54b9033e579a2afe47b691b6b81936f48c9d60cf46782288695225e0e6657cbc3e15c012442969648ec89acace0c0e4aebf269ca6796259ebe43e50de5d068884896b6a7a5824fcbb4adb7e8ccec2ae954fb8dd5ed664fe91767231784e15f957ff8b51d61655fe1f3d0c52791b7f430f7d020d884c9156b80813a8ffe624bb11c0198d1318e20d5852ceef00f62bbd7b734105feb6431eb3b4e7e6640d68eaa39cfa8147c29209b68534b7e9a2eea1bc6fb329611748bc0193868115707f95b86ff29b49c9100f8603f47568b12d2af3fcee2da88fc73a32757be7f7445eb2e753348131ce11105d010c1b59bba983d6b11ed6b33d0cf0315a4cce7f1b2ef95577524700987f4dd41b280b5be82bcaf1d0773063b5a7aa6ea0c93faf96d6ddad7fd4cc9638c710cc65d8d28e1029ed130f4476e5376bcb42bde2b9beb142d5abc6ca00ae509d8b6426c8caa79485eee51429a66dc4c064e315a0fa1fcbcc2a357d33851265ca932f4365846ffb9a2ae3a2865f0741ca6183c80e9a872eb039ef24f4d0f3efee8c6bcc5b8dbf7e41d0c6f8911fb1debc9d5d9e682c102f688d3835be23e87fc84d81dde774a552e8bd697cab40ad798e2449f130535d46a0841952bc5640801185e73c2b2669d1d7c7996ee412723a75624e6714f7ec94581ff7de4763d0f4b132d75499336a579a9617e8cd54673d0e003f1667eebd94532d5b36095fe80c251400f516cfdf252a7d5bd682c7562a5cb6c5b61ef930803218b149ff00f1cd9ecffe4df02418bc4498a933567cda9366540c9c16e60bc4b271c1b7d6c9a8bfe9b4598889d13949be8b98c84ae9a073c6244da1e380a381482b07fb83503422e168f7983e25fb89af178c419732be16deb129f68e7f5db82cf7497c1739c3a1419ef25547a38b46a703581bf5a06fb56d08cb1f1e9576a3fdf6aae9e50735cf44a1f1c8175ead224549011e76ecbb9f234d69d92c26da415ff9b68bfc6ba70b2ec4cb18cd562309a32582d41a2b4e53bd8caf312e5c775cdf9b58ba02a5ab7076d15557b20058a75b1c96d35649e0630995223ee8a7a86f87441662a24fd9588f7ec2a0e0badf2ccd201414a924cd92a72c5fd8804aad63daefea1a576528451afd7784d5722984d62f86d5ce6f35213c22eb61ad971cfbb2f1cabdc9f766157aa1f254379fa7de94bc3b865f226de26ac517cdd2cdf4d583c2b622327c464ebfad2125f963441d63cb8b8fec399c632c769c6f2d1f3df74f33aed77d9ba9c901ab4406ef56b5ffd4a818e3950bc2c656a5795968a2fcca987cc15fec78d4c006b1267c2b0de886bc3fa0fd8c8518827b2bc2a22499ea8c7273318e0b60617a2779bb9697c226fac3bdba57ad632406fc4401021a41bc9193bfd5e010460cb631475d9f0d547baaada5ceced8440d7ed4444b8e8e88da226deda8dd6a44577d76045641357a3c84a5182ee6799806f77057f562cc9c94586c4b5fa1789091adba515254994042b9c42007b740ec671a5ccf523d0c169de02fd76b088e1ef711b58e7646b542c9d5b48d99635269467ad7854199a94471ee268c9d7cba4767938992352d0a304b001d904568d66c9f7750d386ec4bbdf009caf5fe16e4f59e93d25ed01213b58441ed0617ce983e0b800ec24f56dd97ce25431ae51bf02fa64876d8e0f8108a085b72d2bdaddb733607ae01f5c7abe90abc86a51db479dd6c2f0af7af4a69b0aa7643ce4afa06db11d004b34a3c71d9d3ab09dbf9262353c1ecd602dd4d6bfe2ba8a8b586ea7ec6f87295f64a29d9ed5f3f7a56727defe34ede100d64e65ca9b2682e062b9849fcdbbdea251f5074fce188a3f31489c055edf36419a29653ba14f44c7d65b40285466582bb7fde07676e68d002df801e3f300cc153939df59bcfdf1fdd04a845e8049d19e056dd7cda125ac532256ce2d73e22281aeb04ab59f55102d20b9595f62f4d54de164f029cfd6b64664b82942a82e4e73eab5f61fff4db3f93daacefe440f4a018214c7e0d5a4a6db32a7b892f15ae85d3003852e14b4e54c62417ee4526b37b80423ee81800e4fd43e8470575babecdf40f317ef88879d80d9f742a24b8490673bcf323f296159fe353eaf6466fc5d5ee31c7c2f912dbddf38ef70e09a6aec08a472b80c20150e3388643cc66a47b664dd93fbd66a67e66e4271fce1e220881f2c4f6649148733155e4dc456260910aa0ddac5209951e89ab066cf57bcf62c1e7787b6dc57f4879160604c6d08a26055d83909259e0eb5789ceeae3416c33c07061a8fa5ee6b8ac69506d93a89b35d1849c4b263cf2a305e109b4bef2d82882cc3e21ffdef03768514374b7ccd5a20585abf85a75351cd8f872a1bf323f43d7d86500c9020d0fa289c092b1967fc30e75c601a56872f08321ce36eacc2802ddec9625260f0bc5fc017bb3e1607d784cb2552a4c38107c4fbc8d2202eb0994d9864fdeb299e52ea0aaf9f5270197664bd9dc47e6c93114546113aa2535cc0c9742086355fddd27396037bae0b4c2055d75763436fcf8f29136da7b76e0943bb6b2f657523e25f018c755bb8339d3606bc3e1f67e0cf2c86e0b147a2271433b6d6c8d0ffc61a87b14ef30287a1bf1e580d0da549b8443c12c3fafef191b3e29cdb953621a93b2fce6c48373709e6f596bc054a52bf5dfda495d2647592fd7ccfda4b5e4e9a5f72e5eea0cdc3b7970ec6ea54c657b346539aedf84540f47e9b77b4ed91ac583ffd617b9921e3216ab55a536cf10510477c51e40276d609fc6eec0317f9d5a002b9ce0f2c6e77cc14c462dd3960033b88268dff8408c6ea610cad4bc2152f37403d6b0a17d22f2e155304d716b09145366a4c9e9816f33c3317d92104e9685db81da86eaa035a5ca345e4870a62b5bfcc1aa199e7c4071d67f5b830f4d486b4d6541481aafcbf7ffbfd56c961b3b375fa053b612e969872406b3472d17016af6912979ce9ba7724c9ffc8fc7348babaa2b895bccf71b1b3db302e4469244af1cba12b6ffcd1f24bff76717f5ecb3ecb79a848ffc2f5cda9ab656a32bedffe754f088e9278bbd4b5f1a17500d3bcdb8df0838ffaa3e5322ee41db6afe067134ee218fd718215b172f28054d0edcf417271d321deb5960fd42f1034798edb200653deb0dc9cff40be9d8a74342097c01d3e52e547176008ed7296632f010b5370ece763362ff8f83eb68be621380dd72c6b08fc11888c7e80a381562d3d6223d73c4fc0b6e0c90e8fb323eaaa85b279a63d91f466e9e11dcdc89512fa913357edfb1c728dba9ae47d884111376b9311bc1b7e1201995794b46951b304c62e6364d1b2ee928675fef5c5d2478a4ff2270dbdce621a5182af800023502f811e0d2493268d0453be1722ac50933fc2754725be340ec054a242cc283c2e365985b7f09f9f6af4777dafd6604aa7bf9a6cd1f095c302c9fdf9fc153a36a6062d83ba35d99ad3d7ebe964271bdff20fa60a686f8def691f3240ef4a11e4e8c28b8a5402765d9f0b9999f60e48f26676e9045ae6bf389f22b74d021c3f3bfee206c809d0c339b88c9646d6c68220bb11b14bd5da21947a4743e3a4ce9a6ba561e474edb05bf62a4861660fa695f841c1acdfd75111ce73fc33f6c330cc1178e4dac2fb6921384f2e5279872a4e9c4e496afd2eca6c9c82e12d23f632301e23f67128fb82333a9e007285b3d1cf315a096847b25fc26b4ab4b43cb29b420c3220fb34ad5fdea8c96a0a14cdb6f3b10a48d8deb85c9e60bb603f549803ca35c1f93e9adb408ee74c7063a31d9a6669968421210281b8d1d6079f5610805a61396e3790e47fab8e6978088d197e88c440ec883d15a3716c4f655f7b6f3a6a5aa973fdfa45ee23ac130c5c76b64b9732dac3b7b37870355418b8cb22997e8f1ba9627516b934735c0de07295f1a1881f1d174dd992500c406110ebc62dd51c91f21e245b5d5c714d39f90eea7564191d5e2a2e8dfcb8097dd23aadcbb465f59ce37d8236f413716890ad6d2977bee82dd6fd752ae1b860525c77d24998e40019a9be5406a536167dc06ce20690d3efb683839e56cd1cd9fe09b56550a6a7d13f9c9afd3935c229238b1e0149ddc0f26459f520401d8606d0705a00134d489cdbed3a1bfdba3f19144129f16198b4babcc79ab5ecdf772ae30deefe6d61f58f990bd163c858a50d3024c14f2ae240bbce6dbed8536bc3879169365c330c6352493a09099874e3e6d4fb852fb7a380e3c0538c1feac2d8ede7c4fb1d4e3de5dac7ae2d1e07345716a900f1fd523df2b417a9642a62749bab0051e6fe5ad4eb9e26c4d31cb51fceb6c9b5e039c83e5721b9805cf9e570ef000f518ff7c671dee29ad04e9bc3015985f526d4587e98f00ccb5a3ea2879fa52ecd35a5410fb41d3836aedc6c9f07c4ef184d23c4d34b25a0db87d98b80b01f8e30e85c603149c651a6532e3066e0c6bec0ea9fda244a79c91dff54133572bc93988c951c04fbbd74a049b4cf1c93850d0c128ad39ed12761f58be80e308f3698471f5a3243f123296785d8f304a697bc97f73959e037313ca3b0bc0e696a0a9f24eeacd32ef12ad83296b837169d1a1a0e99ed1bec662a375eff46e89aa5fa712bfd6061b49d2ddb9c807f7311b3234e83ab97e0d338261f93d4c2ec5bdcfc52f8445e76fc867b1198ee7ae433b4ea7a8f6dc279b6e247ac7a44bbd927743dfe055df6ff5a951ec9aff3e135082fc7e658ddaf9ae8e0858fa331beb7a671486d7e0164096aa739d6050bf284c3a6ceb4629facbbe61a926503c0aa93e3eba1bac5224d506077d38aefd0f9ae946edb61a5810110311330e18a6d4c1bd93aaf62db604ff06878ebc342f8f72d88d23e067e4db9c69f407b77ea0c91c83d87f421ae9d7697cadb9af0479865188f8e596627ef1c4f7299679b1f893f3f1970262ca194a096e1aeb5324350dd7bd847f9b000df70de2d9d56da15100fa202383caebbf8b14f71d69353a9f6e2886e1e77a8dba876af5aae5829b7b23acc8eb48cecba20202143fc02ce99f02af6c6084ce8dafda52f28bd545568874c94c2f520a3602dd0e302c79bd00ae9f72b1e79989add62393cf03b6ba3668f5b5c2d3f0a69eda8db00c76d52b3e9428e8451d60f7247b047167e88d32f47da30fc94a63f11cf98e1da9b1af960b02dfb40c74e2f74f04115b592996165c1c6b85d7bd24d58a931c210f5c70bedc98d1687801cd619c97020bb3381411707cab33d3fcacab4a88809d8a8df498faed13ac91479103009d7fcf8c08eecf9939680835c8a3364063a0b43079073cc714ad78e3fd2e23db5ec539e961e3f8fc402b98e150393151e4362758cb5f309766e2ca3046bef91fb358e074cff8469a0c579147f69de59d8d021ce2036ae80ee1247da4e909b5373bb5dc9f0171b1937b7a0ff4b688716aa5f16ac0c9f6032a148c3a5df23bb2b042e158be8a893087897325a2241c36b5b159a9182b543d4e9da6108ba1893f23c1e7197fbef4ed4526178699278aa40619861ad2ab621dd8b535a4db4e0b80d1364cbcbb48f370bf17e64b7cab2c510f9f683ba2d349fcd9b89f897e34fbf9faca8de147d1843037e5bce9bbb1965799c595e13acda85ca07f5653ac6cdea20bb305de4bce09bcdabba5bb4b5627a31c2e68001b0c92e4b77224a6e564a937af949c3a58d6c04329818adef620cb40a4ba1609c66ba70768557b0546cb4b83a5cc2e156f4087a8e3f314ddc3d70c7fb2e123e4116b9d467bacf56e0ef9bdfa56f05db3b48e42f9fdebcd80ebf931899968776be28dc668b19d670d36a1179ad10b1637d23f58d140f5127cd6343b5bca0edce16e899a65fde27da2b6b75c1717b748545d26f979351f64f84f1cfdc994b7cbb8eba883c3ccecfc1bf4846adb01ae1b068df5895b696005e54ca6f6067995588b3bfa4c942dafb706a30fdca83b69f0f4a0c0ee6a15ed2ca2264d54a3d80d62e8b1533c6010b0094a7f64e3d86b641e9175dbe456fafec7f45ef7a6c85d5c1a7b7399f2b9cfb1bbc31b76d80b95dc0bb7b5d85ee7d4082b3c07e984a68e4f634011df28c0199263f86a61198924f613cd2c6023e980f47e10d70cfbcdd84162a77fd838434ab9cea294c065f8b6b19fdb6e75ec354569e3ceacebd1ebd0a74ccc120a037358b7549086f916241e3c6bf8faf9d4e58b46b673eeb17b6646bf0263b520d5ba1f01f0ed6275209d9428679afd8cfad661cd8f9cd6a56cfdfe97c27194c3ea623970c19c861c0201d7521200dba4a2552433bb8617091c0a5280c311b3da37896e8ce9a59263a0aa10d3e09d0c7fc442204b2765d9f0262a8503c8d23e1bc15c5fd60ceddd5a9801123bb030f528a2c07a30893f095dfbccf151d4932aa51316a91d48e1d668bccf6583cbfc4774ea7f1cc60693c026e21b34852f0742ab704fcb3b958aca45b3668eeee61dc3222109a2737d5336a434fe7699d4c4db157c0db81f4f772e033ec9b6fce360af4fec487ba47d8abd5a1b1fdf3c37fac7a0e0d55d50458d77c8eb22335ad30ca34c7f03033fd97c342e9de7d35e9317ec9a0854f61276046df1405a8c163be35f2a44a6123b15f49758d21e376cbe210bac2980a098a9a0b21576b3cf519c160940ce2ac22bd5f585a9b120d0189d3561a3355929a568db1ba72d77e9862c9d095f2dbfb4f0fdde2a2525d422e99a6bf65752cb7ec66224535a04c62eb8a340f2ae6cc418a4252faad3ac4685faa8394a03e26c8738470492f6357177b80266d85a7fe7a6580e35e60bb7b195ad273ff08000a807876a9b8bf5c391cc3adda3f9a2da82c45ad8632d83c6ac8cc35d89e6a9c60cab5171e2eb71cfa51a8ced96f65000f4784655451d71e7075f00358eb86817030bf58c993f7741d46ceadb32acf3f56fc029144a6603c515be7b0f4494987a597bd4a5db899027031c193631d1cc19f5872658124bcd2a596a3d56f560a504f9a68c49dd45fac4d8035517187a45e0dbd7c4f5efadd1b3029867d29a187d638aa4c5dab070c71a27105cc6b04d70caae91a312a049854408da3c805509dbf38bbd384e69ecda6ef4633664f3ce5cc854a78b2054be6cd4b7b7e6a527c9e31b5a9138bf2dd8747298ec0b4d85e3a4a05317f7a2311c0e20f7ed636b91b3bbd6dfd8387811b23c0893a3dca454074f208dd4d0e5da55aefd975de108d756972917f430455ee956ed0a1e55a498f561233317fc16b9dbe428cc5bdbba7abc7ab3b55a356ba430765349af6835d348d58c71ef81afb5921bc25a4620a6d1b9167ac667c5647f9788d334b2cbdb8b4d5c8c481e348fd40b044617cbce68a0d734f80332b07b0fdefad7630397059c6342ea9c571c6d4f95d71d9e6807302b26d384d5977d511aa9e9e773904a4f7b04478a29b1b76739f356c3309cafb4a4785935609f2c07351bdf9bfc5db2fbc49f3b495e556a6a922c108bce55f4bb2a94bcfc840b8fd238769d607ab4b30e7b34c8ecad7255068ec0aba743b863574d730ca2cc52b72ad0b3906ed5334aad60f428fdba053ac86629c911cc4243110ddf34c88654a1485648cb853ba3b182b582cec12de5fdc5371b0f8902323582271b2524fbae1876d804895ddaee256d8844252e361e895c23348dfe7a79a97ecf3887b3186f9f8b8060b021fb639fcdcee0a9daa0b0d7e62d96a99884d101c03b0e06276f2c53903e92391d9fa37c9f5d49c0bd0fa718721c56bd942fafeb6c17baa16b4f1885ffb3007198b3b0ac902e6f1369763b1591961e936995a14deaac417424e9acc821d25c2d6e595dbbea124546db517c46a54777355d490554f65e59f8d7271ed006daef3dbea6e408e7aec00c45327483e0b3c4405fde4e5aa9ec67d6b57e7f424699881ac003b6ff8fbf3899c83a6856db12a8ac924367e3744f84e534b5e5db6ee533f275413dff549212d87f756045a35f87d1cf7a049c15d03df7c6d2717656fed160f57fa1d369a8ade23c716550c4e9dc596ef55048e2bac28e932a9d42fc59f0ec82ebe1d23b169626c8f34adff17f385ea3d2c59c3da36de237d81f84940eb44637b88bca262796fdd7b27652cb262ec672c0f9b669662ee391b4bc4eb731fe9c66bb855162736a1d584cdd3593e887bfb29daebb3ca8096404faa5e6a0b4a36382df713c739d49d078cf46fa5dc1d658e408222f1f64c91b817998a6fb039fee4044fb1cb1cc2ec4038a821aec934f175769c7a9d10a64f3db53ce3f1dce53422f1c1c18880688a22390de910c5fe0a7d2285fcb4a5e59f50af3cec2472e4c1b80f08159ad8a76eb54fe2cdee55e8b41ff730f1f5f40d926345b0cb199f72f7946786a88c7786a6b0f1dcbd18977aa03212505ca022793e595f44f042a7c52735b4cc6c75585baf99ceda4fec48d39b176ca1983101a3a5de02884caef7f08aa82b0aa308fe5a713bcc01d841da6a3f412c110d72ac2f86ffde7d6c04a1f8f66eba7002e4de1d0ff323836723fe53ce977de50745a967e4c9d6a4d6ae18773c49083a21d8e04a06fcadbe83b71641e84a2cf46aac0b7c536b029be0f9840731ffbf937135e84f44ca10d11e19f7612ba297f2657714b6ee47c49970e858f8e7999275a949b7002401defa7d613a6502f9e28a4648e62b9e8fb52d913be6567caf5b6ee93871199f7ad2dd3b9893583680a2325f001fc124286bb57c882bcffc4b54d12b7349ee2655736b99733c8fd5b865ebd60e9d36761d77a837c9df36245040250dd742f981f447bb537eb01d2d958e38fe4d4a799d3f135c5ee3a9ef2b5a468c7ec0da6eaa7e132770a9056ea3c4c705120d46e058d566d8d1b88313f9d63c6a29c234b9c838ba0ae7a23dba22e8690a29e851046f7354dc9ba3c3bf4e9831914ec559a1b7c1f475a94b374dda472c1746ff7e26003748df3fe62ad9347a94aed5872f17aa239154139ccb677cdad27e4aee98d0f577cbe6afbe4be2dc932f253f4257e5229a54e6ec2b84df7040d6c6f0a5ecb839f88508e04524e5af79c0ef89c206692a81f75066d6cf0ac98ebf9ed7a0df9b32decbf0f9fa5e596a000b5d3299ebfece834cbc438a8af5baa09cf256ff814770556ea7dff5740ef8b4c69c43c46086ea4eb56b79eb781badeb7ada830e51bb43efab392b729ed2797092d7d612fd03bb9dcbc552c2d05b6613c7e159d8b9a9b9d5119a1e45f6118f69182d027ad63aa2b5c0b98871099f5abf05f8dc1f38a8c259b318be9635c2f6940a903e1ab612a9cb713c1c7f40e45fa5c8c486e37ec363eda37f9c6b761ae6eb62c2cfb6ad660d2dd46c2cddef0bcd1b99550b8d43697dcf7d4c528b565a5f40ea008b1cb7e40700b71fa768c04ccb39f2632c689641094d3c976182b3388a2d7ecba5b1426be2600e329381475d029e10c8c654b16460aa46da9c980917038f33ba59845586d0906a56a9c498dde82a5d366ab80f7ebb8b6a99acbf8065209eedce47fd47210e562e8e460ffec750236f2bd75e73605ca57622817bd97d70897703f1b0931f17f6876f7080fdac540fcf0c4f0c57019ac41145f7c192600668b0f71d93fb8ca4c121422c91a5e19b8f4fbed119cfd85b18bf3089ff043cd0c64ca17b5d2b2a3b4334b4eac737bca81d41df23dbe8cf9fcb04115c93014ed565b7e42d0524669f0b9ec4148dc76caaedad8eceb0a40e546d09af7bc424d244c25b12a83616a4ca910df5b7b8a8f0f9b1411b1922b1f4fd06f84d039d500bb0b5772c023bd8746f18027272d06759df47905ee722c000a0475cf8569fbcea457d286c6c4530ecf5cb04d76b6110555eb1a606a013fffea9cc7a4ad8ec5bb3ddd010970e75780803de05a57217117d61ac0d8ca38974ad8ba1153d634f524c4f49e00089b6b99d49bed3998638b9258eba2cff8aceb79bcd562c271a37520aa72884b72b2cf1ad27d33b4d1ee44f63181ec1aaa2814cdee1afcc2131ca5ca7c7709dd386b6b91a7794111e275d836a15640f18d6906efb57783944b2340cbca8ac451b055933989699e374bbe0c4aa6ef105c7fda721027d4b994bffdb6a2cbe0753d2a8e8b81983659989b0779740ff56935aaf5a7924625bb46bd4881a058525063af553fcc02f8e895f6801f03773718f062f10041e01f5edb801595a0752ed8519af9bd266a11dd68916898fa107d72386275f1f16ac90374a41ee394f7da8dffd9936f17bd9e76047e72dfe1bbb6eccd39062b60c9cd7d0296d1e081f2a37442c434d625eea7ffce14fa9b9e527f9dc0c29c91f8ab3c06a20db4be70cea3069a5e718e91b8e82177bafb8bb896b2411e8b461a6fdeb82ff89c10abbe03f624f00d111b69e22175a6161a12838b97f8dc2db616313010a85d4c72e944eda1b551c73f7716256966304cbc093559ee7ddfcccba7cbe8cce9677e158ae3492600a112387587d2cb8a9e1a81b5d4def967d2f3bd194b68e70cca2fcf46b6bc8daf95f18d53a298c31fcf5aa4d1cc1a75a37b5cf671d90ba08fa1f8a523c16ca705905fafac8e676681a71246f648c4213340f11d0cec39a86bf07dcedb3e0eb4350eee6328e1af078f07aec37587d84fd83ea6bc34a3cee859713d80a30fe864567501762466a665f573081a30df0101b2b274449deeaf6db00db24659e38fd762e66b5fed5eeb950086debddeb87b56d0ec97240c211ed4703c301eb7cfa38d355b751e7ecfaf40bf2ca48ae75a6b8439b31e6a8e8636f12b6ba6c55473d6bf3ff5b66fcc9cce885a39c989d26cb8a2b178f704227b955452ef84ae9bc9cfc38f806ae7c8e6445a9f004b69310b6efb097f45a20cf8ed5a4b6f014b3a8f8470ee8f3e570d1332849cd18b3dcb35f2b51d1c76dee32ace00333041b80ae588ffe8521038ee0a2b778a4b07573b5fb02aeaec85744fe54113d286aa49e36d65b9901c1c7025d46b4852a7def6479c4d26d573be599e266e527291ef461a1a95b5d4578190814aceef02153534ee9d66c284fb3db3f41e8faa4f9f0411df47a4e6338e5ae407216a9e70ee4914a96419d4af418772aff1fbf590213daa6aeeb506e98cee1b9a84a477a30ab4e135eed9cc748a278a9d60b487a7ef8d5949a844ff0be2be9c712f3a647970c15119afc2487481a651858bee5c847eb7e79770b30dbe3178e0df3051c20fe7d8b6f1964a34a7a5c584c93eb43a077d59d8213fae820c95a0592548bf70bc721f9dd26c188b0e944a20f4d607806d5011cf9ec82238dd3154e3fcbe8ca0b8299fb4c6161cbabeb60ff0924b3b342a7f66b868008648023b458191229dab82b978a14bb2019b3978f1d1b334dc2b0d6bc7cf4710677a9c78952d4a9d90c31b9b72f3639534a0711af222d9d2f3bc21c5192dd3d14b0c68d55067b4fe8a2ee131a6f2095c72e5a0bbdca4192c6644cb8f4805a3ac76baf7b08cc10c2cd87d871f8afb77a71401d51c457cc5ad3175853bbd985239513295df001ba056f709183d477c3f22da3d92b33890c1f4fe4c2f7be2d0fea7b634488b818c0200764e96ef8e3c2a496159aae3953ea2de4030d873f458700db21112585f2ef0c3b2c5100266c3ad5f97e7f58ab6435bd80d7a1d5ebbc084dbb0b5b5c3cbd99634cafc95aaa8d77927fabc8615da54a72c4716f614f94e8ec9112bfa312a92ea03101128d320545290dcb1424c9e9528d971ac71eb646cb136042ead22642d6cf320e356e518895f1cb88b6c628c8fa32e63625d5def593dfa59549789d63d2f6e4f2cf5e75e4ae2bf43f38e340d95083712d60959cf41ad8589b6a9dbd0622b3a2c6ef20598058b904ce91cb80065bb6d9480f79f05255e8d8dfd7be574d602a63a6cac88203a00f20da4541142d74742fd2c4b493e033ca5b4259eacb3ad02b0fa2db833a5b027e467dc303d7e49d55820fc00837604c2b28a74b86858457d1bcf0024be720dd5277400216ef93acd78478bd5a9bc3dcb1da0adaa50676dd748f45a1f06821eb64979d90de1f7320640026f4076bd8ae372585eba7c6f01c0bd5adb5e0c29b47e0dd00712f4f87587b3ee32a4fc6f5bed1d8bfcb5c3793a976e602bb95c672514fdddc0032c3dacae0c7727b261b393ec5dcba7de2b3eef95d49ef28e753cd9037701a0f8fc2d96da6a800918bc0c6093947bfda460d2a4c44e7e2ea4b94d08e9d6707da14f6e54e2e9fa46b98f1c3dc40184f7c089114e0339ba2b501f7fdd4b5479dd1b7f01a3d46b1abc9885ebbdc7b7ffa5a9a2eb5829fb2d94d8d6949dd95cd767ce39421098f71f6305f1e4fbd6006a80cb82b136170a8a3a1ad252544611973fadf2402ae3d52d8b0c8a44233af978186ae5f1c007f4b5b160090043e58c02a48edd8a82a89d6f97458ec6a63bf5923a1937ed6ec153e869dea2fc05dd2773fddc2b04ffb59867ab8a1fd2aceec7957c191c230a72604ef0438ef0bda04bf6eede144be1668046057856b53d0492111af45a00bf3e23237522dcac397ba6e50842b46b3c8f4d60ac2d55721f4caf2c57be7049b0dd7a205973ed10e56200ae36cd089c4f0bd0908d20eab61c87fe9b3df3b4c4f864c3448acf96c34d77363907540b9c5ac4ebf6b55a0b22dcef77c0eb179b8c87c141c59142ad932971416865ec019e9b30da3399b62126cad1c0c23db20b107e84ae6eba48d1d5f523260d62b6e0856255645b3451e7a38255953068a6757196a6813c545a7aec4616a6566f47392fb0bb0a406b84c1d4164ae62355ded3eaaff29b29e6ce5824c5237d9a50f9d08601a3bcce88c489a3c1a77a95feab62222ee2e3fd56ca07d97a3f917a7eaa375317854ca5bafc445065ea16d9c6086742f4a6ac891788c05964aa9edbec6022e8e90c6ebb3133332cc61e910d842eb1617d79902cf7114c6d32033fc7d1dfa503e5a404cad107d9b316159450c1d012f0be31fd4cccf94b785b2722a5443599f9b22a5276dd899ca76a45b00f168a208db335f2f38c61d3ef55c1cd34ba2f8d08896884edb87412b0e7c44fe15dd2750a7a1e5a82139501609375b884256287193ea57d476dda3229752c4f2661c67c33668f289f52d95557bb4b5c934fb8014d21f35ece8c6a2d44a8a6eb38b88883890ebd30cb80de67c97c82713fe0f5bc3f36f4f6027aac42c1fd0525ac3d218e5165d83f0ebcf3ad8eb286c3e5c329168f15f309c21473d125b31356494f97dd8f78d5845d98f280a17a394939d5e5a26d84d0244719a3b1d9d610b03e93f32c1d68fe499cdeabab8f8b00105355e8f1a3648b62b0f2c8e742cd0e72a5b287f3dd4a19918069c7f1b09954872602f61a6214547c4bddaa8f89ec52bb343a89a2a912ef50af12490bde9a953d07785882eff0d2c5f307a1bdf012b1e3e7d23dc8b8b79f2209ee7111d6da925bae1046c12cdad87434b06157f4f059aa2a9234dc0cd252d2514fb29efd11c1ff786d16c1ea9f7deb1357ba68cba8c5a24287236dd8124ff82ef358f024fd21bdf95af23bcba018a6244e07fda10089fd26e593204934b2ac5f5ba153fda1f0795b7a04e95afc44641d8b4ba6cd3138d91c1c0d59c060ff4db5cbeef00dab7763d561b5b955d460f816d921ea134affa7c71e6aa88378bd068940e27778f389c5e4c0b0aa911e7f1947736d5df03d604593e0ecfa624106424427f580f9ce41cd8b4841656f32cf814393d5ea0ea2b4935702f793bd38310f8203feb851a51c06bb69a343ddd4d5db2bd7d3da607c48b2a31d3b718d39456cb2cbac14d44a252bc520970429634303af770af3ad81e58a07e70563ac51adc5fec312a79fdb31412192fdbf3f7b82099f5fdf849f190e14b6a064a44cb6e66c6f3bea9949010f93c5cc205bb68be304ace3867d8fb9ea9c2686924c0d08033773010e96d0e7cf9212063fe6411c5038fa9a851c33684e72f35341429792332b12237cbad9dedb9e69ecf7bf20da8163b93722e414f578c8a4ef4ac8f562f05e59b8b607201eadb12bc04f37cf2880b322c9c6495b8501a3963ca1eb58b9f26e25f4ee662e783dd821fe3868e3790824346aa0e22c18acf7a94df54cf673f2fb7abdd0aa354afbc614c9ad61520c8f98f8132f740d717915da12d2cb75f328646a5836296897d972e007c8cc339988dfd604f4c89c74d5589c1d8d29e0576bf56566bb5b37ef4fe53f062e29cbe007e7c6c99b2dda6e6bfcde58ceda6e250197f4dca130d40c4fe6647f9aa4e63351f3a39ff4ef3d28dd1895fbce39aea40a88520171520fc2087d96fd43039f63cfaf00cf14ac5b889caabba14f58b59760b6f462529c2b194d99630201590d5e1f009c47f03cc295cde860c13a77a4034ffb0ba94b88899884012af53769660c9fec9985bdbf87115cf002007123a62a95a37e8e529b267a90c8779c0806bc9251231de2785a83b59ff97245af58c466886bc2dbacead370fba2fba6c6406fb048fb3e1cff3d165f2b3ca969f600083fe9d303cd05f2336eea880f971a3a098125cdde9765ad0b6c299ea458c1f2d1c9419dfb19f16a94fd80ca836caf0d49c4b36ec8d64aca7498cc1edfdfa0286cf9dd5c2eadae7f7b22e2c65a4a2d060a37399d94d584a7d96528d44229f7e8f2a51090389b9ae4629d58b90005f78615785587a1907b28819488e3792f25c4e9a31ecc959443a7729d68147861a5f0e230b023559a43465e7cfe87153437f90f599d37b1338ed2e9a3be317657ebaac6a682729a73e8e6a6aa53c3fc36fd158f920d8fa4e7ccf39a510ca1f2d0ec1ec96e8d01b891a8f46a6d8ddd85de4dae52b79e80bac6efafdfae35df2fd9f1b5fabecc1c5671969b28b5ed492e4690721151d515bd990031c649b708790c7dc0cc070fbe8f85f0e5746e575f65984f6864410d9699e19de3e513f172f4d2c68405b419750343ff9125ce1b83b352e8490dbb84225899884cf31901321bb03ca92ad3555b48e89b8b43bbd9596fb79275b224c74998f04f348897e188ec38c7cbf95ab4c86a9a1312deff6aa473155e760dc81a70cc126c7cc97140d7bde7177034692653653aba400eb5b790fa8f8b97b70ed01af28bb818e4080e2cd836fbeac1e7778f77bcc4793c290e7fec20eff01ce5d15dacf8743a988054eba650687fb446602cad74f094823c027fa34ddaa9b88a57699083ad3457e22f61b38a5e62e397af1035caf36f181e640e4e3332daec9cc85ff73897414145ce06f6fb47ee6ffbe2670aade5741fbf4d115175311477b01919a04c708d774b362e55187ccee77aa2a9d583591e8ed1e2e9e29ff9f2ea889daa3804777a8b246c2852a9b32b3205e36f5b2e04059c0c73fb65be3d51080c3a0fcbe333b5bb92bc09d2d3aaf77cf1c5a82d750615d978539ec525c7db63d03a63fa6067aa3d96085e2c85d2abf27a99fa1a3840db803cd1e5c620b2d9d38115fe8d4b36c618d1876917a447bb571474fa42cba01a550b84ad456d7fcd034f73950a162f170df565eb9e1f5ba479ebb3bf24868f27e2fccf443deae2782e8be41ab25bef9e7ecb9e796befda08b38fedbcb4243197788b740aec8fd9762fc410ce1717011f040baff4c9eb1f5f0d80eed601f3bccb4b4eba7eba9f29f7533f542bc79b375026a43cb7b59e06bbee1a2fd4fdfa8b3f581fbbcaa8fe342f4963c060243b8084e48caed0de239afe344acffd5e3c46f79eade97d648cccfd762d11731f78bf30833392076b0a48def365ac38f50fd64a186b9d4d4103eb7a70915d18373583c3ae45985cd4ea500b73a7236115c0d24ce8843becdd5bcdd435895d111024f0f06ab848646b773624daced1b2c1534e223038964d3fd80565763fe1952a606f75d01d8ffa0da978ddb3a6ebe409c8b5df427bc07c8c64408ce5006cb2736393c9513ac3e1ae700bfa41671747de87c971d3cfd3c2cd3dff2ed529633b2a40534c5fa5a616a16bbaa19fbc47d5df12089388f737712d11dd6095bd3205ca4b25324aa20cb9c7c9fb624962d57f085642f8fc36bbde7cb7fba3d4edb33e0152f7e91459a6563560ebffc074982579bc3ec9b4da4962e62303c00754aaaa56c854401db480dca411157a30b14e4562e88acc93a2d29c67e2fc8946703f3efaf251cdb57f93c780a76c28fbec21b2cb6957a9fe3dcebf4a91372b028abc2ae542bc0be4d9513996b9b098dfa5842d7636136cae80f9946c234857809a2b63ca101992c1c6221d418dcdf29159cd5be34f9c4c84340c52f678f67fdf5f4c747b471fa250a6c1e2f78dd4ce6a8338e5ad45d9021882cd6a3b8afd8a7037f300625271fcd31492fc9c9dbdd006c716fb2e7ed01e0b270724f730c333b5cc71beca1a9e4f62a0b496940cb186a3df78e6aae1b7f3e0e9635032fa5ec073db10ff48e0425241dc2b67cc3068265a7acd3917aee92c0a5516c201164861ecc16db44b7bc362e39952577ede7540b13ccea3678b4c7c24e413950d54ce8c927093b27b50b3f04191e90f5886fa23e2a565f51f6356e0a947eadc1a6d1ac6ec0176d45992360c396ba064a40ed7847de15068b2cfe4ac85a84827104c42e3612f535beccb348fe58f6e5e25ae786b2e4e712c16c8b8dd128cb426938eaa8f8a73a55761f17672dff5976130eb865852f5074d90b04f8934bb5477ca2fb064642e60635d2abcf55024f6bb505eaae4aa8c09d24c8b2ef7abb080d42b992a7cbd955bd94d8e8e6cbb4bb43c9feb66341cb83719243132d67a66e60a504b2c67d9636cb5abadd68b37a153816fe97b15302ea4611eb54412d071c976bb325b05542ddc9d52056b8b7bd196548213fe80c3b24796ba784b433ca9dee8048af9290df03410a2b81aca6863a1a24e2e1a45da5c1e26ec483664f15bbb14c4a5b3faa44ba328bf636fbf43e0de722838d281865b842e53cd36e2b447d09a5f6d9317d3a16f5fc409910d291c01d5545da94209fad19d97749b91f451fc22511d95bf699c68ca90c16481694bd7ea2987c832214a0c215e69730e60fa76d8ce82586677285c7d02b188cd6878c4b1c537e458858f896765b1fbd543e4ba8225f454d4a99bf370729f2f2fb1254acdaaa552d3f7adc373e24175ebb2a1a12b3c787379dfdbfc99b52c4dd0fe010cf42217ae3161078962270a5dfa25a75304f9b09423b29449ba8a3a73e7ec60c410dc01fd8c5a947ad8ba5af859c31b93e7c37ce719feffbe47616cd1dee5d53879e5ffb31497f8c5e10e2ec293d693e9bd24a8a8db5cce8540117d71c6255209f16a093e08d79cc7598c155329b04ae021eb26c4b94d3fab5226cfd4a952c524592cb15a96eb5ed35718260d03615891b074240fb224ad838e17ab7e10d63c14d5ba22e04b74f15bc4c7af4f512ee0f4a1ffd25c1fddf151d35be0881ca28cff70276da3833048906bb2997236cb399969106725ddcb9c47333eeddffd30748c8454e5287ecab08434c84f2d7a490878f8bf5850feea054047824a5303f56f98f6ae15c745e233a7fbd6674c3e4f9c80eff2031f83ae1628a216cbcae706523ac8cf1950e6a81ee2bc7eb34a043b4a5a9bbe6b84173d48285f2eaff1a1ddae00d89219feb292aa262cf77cbd9bc77dddb3548d0255f856764ff740520c5a98eafcaafc59317e90f8b6d8e8f25a9156e1c66159dff7c63e175d2e62052a07bba01abc3f7e9e17cfc6ee4bedaad9cffd953443633521755fe21e68a852565c7fafcde7a1c175340dca0ff91e561ee8f34f24b1a57d9794c672d5c3b66416efa764dbf0021140c57a21d836fb0b7cfcbc764008643f2b41fd15c035f51b49fe969f255203471e7283706e11110c7f993cdc55381aa21e01f6903733b092f3b640f049d0c44613240ed644b3875669b69fc82ad40ee83c4c18d9c19b6c858582dc3eaf8712038d1a80da7600aeaa6c233391d635fe29d9061c102820af5f8f7acdc934605aa8cb6150187fa4e039d58d63755b51186961a26c5c7ebb26daf5a536bfd4544860186dda0bb882ff1af9a012d0aebb3f9c3cfb46938a0849f5ac5989fbbfe32ab648d9929b7bec1a3c649b5586278ff37cc32bbb61454870a5ad4ca2a092d0317cb7faa164da04004f73be1bb895c79cc429163fde21a02c7ea957f7fdaea73e7de2c982e7c011d68c18e02a3c64ef70b42489b780f811cb48a143c07345d1085a856f684d980ad173a5a1c6bf627cabffdb0958e9ac9af73429162fe6ee61127735ecec2a1b5044c0ecd90aabb08010af798d098e5bcef61600fa89a9c9257a419a3139908e53d6ffb09663e7b04e8425903116e3fbd379fef64c613c3d702245eb01c191eb17166442936ce94b8eaad82e23c24359cfd4afb7e0c3bb0eb6217a24df03a800e3a1d76f019dadaf1680e09a98d62ee90104b913e193254766a09cef5f996d3c5d4767d6b4492d63835326dc4b53f5cbc340fb7e8a5a02020d96bfac41ab640773f8c9eadef34051acd377df5c97d9983d7c8a1eea66feefede5f0af6d8ee35d8c40ece6b1cf5e75a146ed6ac9c7b85e2b8bf7e44d4d25602d8e9df078eeab607ded8b84aaa5b0ecca0f8bb66a96ccd84ba6a966c9271447c0c36c02c8cfab64b30ac150db1e59fe9607aa5eba7a5bb909813d56637f703f1b1c2fea39de6941526ef904ee34f2468566d7230833b7704fc28e2082b9e810452a6caf7388c282b65f9bccd5a1c51f92521b9bdd018cbc4e757831a5253995018d4e2b4bc775796bd530214fb04845bdfda221cff8b6b54675fecd59248eed29e7a5e514e2258d699294c33a6e6a9d426bd9d1e253a57bc17a639c91ec011d4d3c033e822dfa2c215b8c0f2b7dca6f22dd729a9c9c9d5af2b56922fe552911b1ed5a79e8a140c0d80ed915aa06d396ef88105c26bd90f4e7a1bc2f03bb58d25e5132fd73fa4f075c0164ed5ecc41496a63d66b981e75cd852f550828131bc76d64e202721bc4be9a33c708e878787bda22062330243bb79fa8e2f708e3b637c15fe04f31c296bb96a74ae9ac6dac8768b9b28982162528edad355386f0a1daf118480c53e6df7ac8ec3a241416d9afbe32edccabea70d8b39452032a9004a0195ef21a09cd5c04f223b97ff6a287b66da53047f776785c197697009237b787f5690a8fa726e1209075edf5a78903a4521716f795f6dc193af2356d8ac655eda97157c5d68bac057dd050b315404942a773c2b0a9f64936da668f9a934b3be9f24f47b2e8f9c5b8d2854b1f9f82573425b27d6f0834a45d31a184f68ac685eed1cdf9de2e25367559aec4f6072caa47706f189164ce26bfdbe8f500dcff14b353a19422a4754982c328377c2ae46eb4ef4cf4d3e197e720b5f7381ab484ed34d394d351408ea3b420826bdb9725d9e026da89eba581e411a0238c0ff3fa227f878436217fbd533c7f94c00fb5d9a59c13eea99f05867c839f638c38e789589145532b966e00e4a89e2bb069128e7daf43fea0ee95016070505f3cfafc3099ef9bbb79fe014047e415ab7c95e89fb64a4e660e0ca11afccc9948f1e97cad0027c60cae402117dcffeb9867e8133b89eb816cd6a5167671870626b1ae4083f7720020d26567c3eed647d77ac6453eb8eeb4d5183eb79fda6dceb9155b2a9cfc49d2ba8fab8e14343092b71176ec8181e2ec9efef1901a13fd745ae292f18c47e9beefc899de8fbe6f6008c18c577b086a625c5c92ed50572ca1d36a3c64a69a75d81b867630df5f1b9828e546f3312a0ab0893289b7d19fbf8f394335cb3d65427a7f900fbb4455285cd0b1700f1336335d4dab9b8596d7831510ee4ef54127ceb2af10c6f1d26431d9886f40178f68c5b3eb29257735ae737fb1c3ee996e3f1a71fbc8db7e9bdc377b5280fd277aec49519b312ba364b7a75b854665f7e374203985e5d62b03dabe9e9268d13876994308be0ee5229f62b5ab35b9f1937e569de5ee98335719585a662e54681a685cff73dd191a23be613f867b28f8cdc9a482bde620935655690499eac320385dabfa4bb7c5f29f12878cab99c68110c44577f7250bf89814e7a038a061eb90b88af2222bc9ac668dca866a8a0f5390e7143c19fb391c82dae9bc948c03fdf9b33151337cc649f184891fe423bfc237f4c0d7acaae32cd2929070bf79eaf45518949d66a56f0c7f77b9982dfbd7a3d34c39a4af691d2e22020759dbbd5224a132e7495968d9902899aa542c782bc30b68642e8b66c645f318be4d64c297b6d357851f2dcc4a742169347ee4597061eb022a681c24fb7f1208e23d62e579f4afae0bf98a399cf062fbf73a05535c1cbaab0c9df1a59f2ba2c8fb89e213fa084f13b3d74c1764857a0cb8694749e7f1938a56e6c65300ffca183ca954b9a68bb22a854cb534199247f363e6991e7753d78cad91f6f8f4b05354f988aa53a47858ce9330605a440a51736fa47e253b90237011dfe20d0975f95fc5bb1c36502f6a0caa91556b164f0cb6e317f78804df95169d1151f9384a8ed5d7ab7afd859d3ba1a68770d13a2f0c2e3e2dc8f8f8600cf8ad63819f36981ec3057c2545bae3ed00bc0795d75a8c149cad9396244412f5c5cff2a2787eccb0e2ac28c764c470b5b2ab2690172dcadf4d54399f5674cd41e04b2bbc0a2af53bbcbd027a6d7ee8ef74c9c2a8e6481b86edd85bd08c709098346ed66068ac9b7a4ac5e17f70ac9087fb88ad4c7ce5bd2d24f059411f6ca5e83fda5b5f2afef7a0bcfc24624d8106a2bdd1c3d8dc73a1605246711f9cd363a008d01000d521ab125c310d198e5e0e4d073a668a412daa139a3fb14a78fc5745159f14364b878754571209426e8d67b9b044104de3ef88884fffcca56cea6f7d200773b5ecbb600b1f0783168fba92ea0d19a1b9d9bd08d79de1df53fe860b40701fef9f5b99ac9c788c47f75d0482912b4804fc033b11d92dad1f075281df941c006e029a315cc630e97a0b58d6a87e46c85b5c1eb363964cba1dbd18cef4a49b74fe5f231f0c880a0d99465b933097704ebddada6bedf4626e0ca33d078d234d0ff4556b4549216ff52cf67ce27e4e78873cdd276b218f3fb542b9b062e4d8a15eb31a8bd36ba513207f0f2fe397a950f73de1814806197e48e741ee8e0f983fcdbf783e43517adda4cf21248c517cc76cb8684cf5e92de5561a9746573b32c91b0d580f95d0fb43a8cd2e4d0b2c1102ed0b9d5fd0f2c2d012e2baf0aa70bdf5d870ae7908ef64b7c24c4bc9b318830adf7bf244a1672b70472c33d5660e1227adb915658e11cb8875f52af8dadcd34ca41f2cc3f18b2298f05643ed389eb12831388b216244cf7d5159f7f9afba8445cb9cb5aba0724f4f346aa5029266656298ed43683daf324bed53537ce64da3c0544c030f7a932ffe6288b1a406ad2c5741a0a72655a5da280a523b01f15dd7bfc43c41d3cd32771ecff796c03901b2f93f450db0b5b836e8489db34bac1829ee0b0003746144686b9307a2bb060fae69d094bb0c999e8ded07d017418cef7f484c1c5c478f5e6012323279bb40db60f45a431632b9f23d21bf5171dc97e9c2e696b83bad9ab963869e44208ccefe1b5d07f72b4ab81cef5fbe41fc783686de993cd6846f1d22d54c18c8fa3e4158fed76cf276e6341f0a033e8a4a54912c0e7a9823171410a108d77f8d2761ff33ed3620fc69b450bbcee6485f6047bbfb480d256b819b057d478ec1ad524196b478755b4dbccf645c218376a3ae9dcd4906304abdf4bd6ad061f41d226b4831f7384eb6f2ed6a9d8faa7229304b66176019964c2d433cc7db549e00dbea69541ed1628b7ee602c73ce0e1da838dbe4b1462449a9b1f3efdc53499c0ddc91d1b5cbf58194976b601c729d84e8a6894bc227c70d661183d0b5142adb2f295d71d217cf27c30027a044988542e670cf5d577b9c80d2e4b85948078520e0c696d00754374b7e286162c5227fb252cc157dab35a4e9dd4703c61da7f619957f05743673dff046b6e50801f55051ed11b9ea502e21a76ad2053b8c37020831b1a3506de12e82834efdb0c056b7490507e077001ff1b9011b6c9d00001b1b941bf55ed216fe9acff7630be87bdd6dab4a580150ea967234be99175d3a30ddf3c877578eba683c3df01509fb860fe621ed5edbcaef1dc04ec1793a6c3b6584923fab595a6692ead26d5e86ed1a68747d507acdbcac3351b9854fde4698eb5d701be848eba11719d3cc0d656499371d15cc13bfd3a1aa9b5f0e2533de0caac544b623b3535a933cb7e8a6499bc0d05f36dbd3b74addebce064dd1a20297814ef7ab4c5518bdeddbfe948356ae8a7cd23923572795a1cbebbba82dea3507ed7fa21cab2ad905da3ef3a0a4d272fcbbd82b6b35d4d4d7d94ef55ff9bdedd986df929051394a199a672bc58161ce8eaf1b66ae1b57d7c2f1271cc2a2edab27f9a77c8013a5d1f47dea9f242a28a9c4542c6843abafc5314e366812d741c7fa672c94c4b82c5b979c156fa5319735d335561348e1fe7e977a7ca17295e9518204f1715914c7fade6b6c497c7cc631ad1849a8e047961cb552ec6fd8a1d70ed3569055965d54720719f7d97b135de479e190981056b6b906cdab6a1f1d26bce47087929bc36f81b5fbe88fdd2363cec676905fff3c35b1d8cc12dbda11b6f958dddaef44ed114c7a6a975b035108426889dceddf5185e33261048ddc67f35532914a2054a53c12455c9d0410884fbb34788ad99ffa53c60e1b37491a26cbd7c64d67762bdad0bb557fadfa41dd7c10e776283bcf0e8270c5bab7e771c894bf782831343d006b28fb347e4d8631a0dca0438e1fbe0d2b345201d5293fb147257b2e607197e4014635c20501ab054cd48bf33694cd62254e0f181adfbd7493c1b4f02af5138d7ba57cc7cbb2e3035210f7bd9703f0cc5cde3a20f8c2d330741de25e912d733866f518cb77d47b91a6dff8e8004c76f2546d915c72568030e20dee033470525c30aa2e38904d693d4f57d933a2785099b53821ecd25857f5c6355cce2d8127bae6215ac31c62f4fcdd67035a585b44b93f281806a02a9b30a9c073bd7646aeee4c731aa411bfb52b5f19c452ca9e12f9fe9b8cd976757b89b9251784bccda513f30a4519c5f100a0d9d879260e4b7a0367645cf71cafe928eb452e3d9424b5167cafea9b55842de2e3cc9d2a7ecd7f35ee9c820e2f539f63c075a56c5371796d19c5d33348f741ba76fa3fcc63b34f36e74241e0025ef8dec4e1318f46fd7a448d045c30df3280b6335c6e02eba009c581d63851407abad1a5ceddbbc37e08785ccdd979b6c1ec08b92c9e21c12449883eb82f14339be9531c2b4e6499a84facde2935f8909a76c0cf31dd24455808bb6b682763c65af6a8a08024a415ef6432fd755f204c33a240a4aecefa0a2d42108ac3a24f45c3bbd106381ff5b327f92c7244d4636308342eb457da501bd65f5dd786d7e89bb4ccbfcbc5018a8faee1b662f651df0a3d604b70cd8afa3467d3433abf17ae7b32ef509330556299134ed95176c5b0111321962946c16f13aea12e51d5024441077562543b4aed374650d19600163aa782b0369e4d217faa973e63fdc115117485b37d329c47386f4f50fed92e9bc2281b7989cd3de8a25ae21018e6b6001c2c900b85d20f350162372a22a8a2de4c8522582ef8f7e5869a3dcb09511c8538660a6c6ec0773d2d27321547a20c9643cdd4c27efebd0da28dd7a2c3b13d5f7515727904df8c43bb051d5782e143c8e85b0d46969a19c78aec959beb66bba011afccc3cf87979e5bb5174cf9acc3367026076dff861a441bd67e3bb99dc2884ff85022a089c645623beefa94eb2ad0de081e340a2599862efd906e698c493d2b3f9d7d4d598f1f5f8c9cc923dbea385e8f754d325f657fc2b09e6b7c6906d583a397b072230744c5f8e525bf429a184c5c0985910d27a8c75cc0091ce19d00399f5a937f3c32e45122fae4457a3a9691b20c34a4a961a997050e79b4db9b6ed50f8aa64d0032c2190f5866dd6e48d69761ddeaee4cd042a308860dcbc882b1f278729c2ecba7cc4d20f6031a00715daf4e8f4300889bf0a0bd6647f1fbca6b6e8d6d3b38473eed3792682a3a124c18716902417b45e51471e91abb797753ea769e94d5039eba6713e28ccbffecb97fa780a514bf7a1cea8214b7968475ad67c914948b2eb8731bd6c5e5743179e2ade7aa5ee87fcb1486f10991bca020428626b8aef93967239a9745a989a4aa0943164cf55dd76d803abc701da615f5c42170944314671d418a7dc56868bec881278452c778847a3b3426ef2742d1b6ed802228fab4c09394890b65c2f6fa3d72b357bfec45cd6beb0c3451dbba84fde1453e3b6d5d6dbdc58d998f0a4c574e93f3012d4721f1c0a9298f9bcc2fb16fb4650a82c93f5fb7e7b1da82ee371c9d50005a50499c770d35e3ca9666ae8449f43414d00f629c85d3e5ad83f52b84bb310d536b344fb75525961780947671c053ee26e3d9d19734da49768f6849192f941787daa6c2e6756477d75d2a9d42ea05b542b70cfe4fcd9376f1d98f346b32d7d3d5da0aa9404b4ce51ae7b8eb9cf0d4037aae55809bef3e5a0e0f0fd4b8e7532ce112bb02f63a2338619c27e90805812fd664120b1da4d15e23b145eb7eeb0633a7623fc216f1c21685e8363fb83153e0129b6d286fcda728beee44f10eaff766aa2b56da3fe6305c116bff0be87c3a127da5eceab260ab57ff3415b367fc92365a4014a2fbeb0eec40ae4f7a56b60f45d5836493f79fb95819550a98fe30657e9712f74649a3c7c56408934947cc65471acf0888e23a1766eb9da5b3c593fdd936757a7fce172aab7619832452c8b81e8725831327bcb7181da280db353bff746b9d6197fee9052fccedfc4171c6cdcf45b8c27166cf297263216e36e76782b6b41d340e82bfb775dc32208e161669fecf228dbd01e556ecd6e1c7127c7e32f0d1a4db30e6df98f92abfaf49e7c68d79b90255fb21a41d66b546d0afe71da04eddb8e3c0b9e77fa3e0551200d9c27f1605523d813faba04b69b822efc007471a3f7dc3a368276e94c52ae54cfbd9c2e747a2226481a2dfdc4c61a4aca82a068a4a249d240c0f6b443bae6f6f1743d4f9b5ee6a16d4c156073ff6714c5ccefa77f993c37df6e0484787abf56526759aa01b01e613080c483d3a7845ba9cb98cb7616c7113347b970cdb068edd677f949edb273feb6067ce5b63bfd22d92ed1d4788c64628b3d2e31d926cd9bc86d73a66293c99b6d7cfa0d3733baface899c2911ab13ac13153fdf2862a759d1850df9c256ec7b1ce4912e5132e272c54ad0a1ebf00295d5681c593434480c3ae16b9de1ca5bd6c60f6a3d6cb6fcf6b653bec4b4a6e00cb43ddd6a86fc5c17ff8ab5b1abdcbf67944ab81faa29dd811f02b5f3831b0fcfd6bb5440451beb0de41d4989e85667945e9e9c4b41912f43735654f37a22739383bc657321cc4608486f35931c2b18b11255915eb73fa0a378693f612f16230ce9cbbb91cd7648e0ee5000eb6f982ee0fc0ddc411fda8eb0a8ba3b5ffde5e75ddf4ba74b50b73d18248cc2f204a24de7992c2a89324ea220d6408e65de72cd76993eb5dd6ef257f6a40594fbbe63e86debf721058cf5d7c98b62a0ed4b151e625e022dd1b4989f5387dc9243911bf2befe3deabaa381c5586c1c60ca243da4657fbce3c5c5da9680e670b3948114968e4c6bf07e0056ca31eee97be19fb70b2daecea33f097a6b454fc120325813e787a06e545feeb79d5ea13e5ddc04991441c0731215b9bc5b6136549d05ae840ad1075ec48a24d349e3b3498cab1c09562273da960c4a046bd25488ae2e1a907f0878617be364c3cf495deac5e658cba1f1969378680b01a7e89f592f258e3831ef96a64cfdf1e83be60513d8d66deb123992576714f22372f7dcad30eea3243988b9de73a0f1424dbae63b316613d2d4aa9e374b502f359cbd233bed7ba44ad9a6f6921b72eede2b0562c8a817bb0c50dc2d665a467610cbf8447d56a50796458bf4051c467477da5b0715e687ab913bd32389b186095651a717d8d25d7722d458a889add207668520708faba856c058867a844411622e72421d1d7fc3fd851802048ecdadef7038121ea653fef01dd98071d2b00bfe67c746a6b54f8949860bb05d048d29ad027a9ea5399fc8e55653c585fa8444207d4d4381c6e5656b422a8a0ac80570b73a3ecf786d99e21b2accc047a4418f0f4faf91dbdd43268084d5fe5fa63e66fd1944b1245b0358d2736857a149302c6d38bee312879d82fb191ec4f1154dc53eb3af5ba3ca8d43a2f2c855ada332aefd1892696ae82ead0f902150d5a688e4ba9d384b619b7b0b9f062be5b45b6acfaf2828a7fb10f844ed099caf10cd05be092b3dd1eae1039190bb2833151fef39036eae1b29638cb17531f4d67548bbb503b26746e49b3bf92e5cf0460d9748eaa1ad616153d92acc82ab80247722f68a2918f421ed7bae2957ca3522887fcf6ee67404e1718ea3a3e8dcb326b7bce835aaa0e32012b89d5092b8eb2fef79c094e218f0db9bbbc26f8388bea4e50831825d85f29c69d74d7dea64a93aa4591808f6cd428a405ecc1fa2a8b2450f2ded64a37fb0fe2907214c2dd1127a260e13ff7795dc745aee91b4ba8208619b9b673c92fb9f7810c2226dd5ca6c3fd8d0c879493f9fea1e4a8a41d79380a6ff6c29e881828580bee3a6f70227ebfb89a2a7ea05c5d1246c86822c685a8f5258ab0ecc8a193b4503a81cf8bc502b63ce6ecf3e76efc3b22464bcb26cddad4aa9e392ae2d55899194bb942172768a2ae56cf362feb223357400d71ed8ef0871e758fc66808270539222dd700871d701285812f4e8fc897904a602e019e63c57014fbc92f3d151e1ebda5b1685ae24fd6bd206e1b96be0fb1cec24f5deec77f53fee69953d4798f339892713d7cd0ede59d5332568548b3cb0ef4af078648a6430c38eedab2aa915b518924fa85a23ffc3e32cee4baa4071e07cbb84aea0fbd1b850273e457f3d8ec600907c711ee30d4094effbf087d0972d6eb0a64833030b6c4f66b2233f649aea6b741a823d3363d793ed7c9728a88938235ce8261d8bc0c12b87365df319cc1b267438ce1a2e37a9d3112cad01a3ea54a96415ff8513dfe46314eb3e02a1cf1276fce3977089d396549271aa0a20e98d584e2f1b9dea3e4977b6b74e792272f065c856df9ad013291b340408d2bbee0be4b4c884de70db8d7e9ed256a0d68a6380af72eb33752f323b3061940050549a749c8d47c709124a30ff3a54f539ab7849a37ca7333b37d7847f0075f9b3179b173366f080bc1dbf167b26b8ef54159aa8e9c5b2a3db04f12d2e4758c96ba6ad5a1a694b4d6f3fa5ddfe6bc85e62a5cd8d6fabf02f8d54c75f277bb56836b167a3a0c1a45df5bab8d0e26dec8ec21263fd6f2a277b0f08de91ea03c1b0bfff213cc69a69dc89dd7b46a752a69e2cc907b07d80367370c049409dfcac21dfeb46c1022fdaefe015d110f8082a1dda2cec36ff53a9f22389356248a05b3554998031ae82aa1d1dca4cb9c3237ba10f6e281189eb96d1e527829553e9a92f3a7d357a24af4be7a50924aefd00752fc1a6a0969419ddf32bf6c97c1bbaf308a62fc157e4999ad1835819812797739323ef1dd04f2ceae09b3dd6bb50e4d292bab179fc8da2913b479c716ce52a8971f5aa7f846331997edc3e3eafa9be10ef424861ef23bf550509113ae535fa600833c143959d85b50cea7ecf3ce04ef53a654ed9aaa7e4287dd90d4d42ff9e852b4ce09e35dcd77e3ec26307f9e408df3d8dda0d386311be654e7aedcbcc0ca3f2a040518ab6083771cad1eba404c78860fa244965d9704859c18bbd7f791dbcdde611bbebc7977647bc6195cd1b6ecb72001cfe3c470121eedf33e2c2748ed17029f2cfd57f17ba1cd77c7e0bd20053a9349516fab72ca214c98bcd2764a9414103ee80f4b7bea0f314d74c50ff6c8517aed20c52994199b9afb125f29f3079fd124df917fa261e916ec9402495dab70d282a1ba25e0d1ae08dd41ed11ad61c6978e071b4031e17f82aa86383966460e8f4b8c632e519e2eaff5b5a83b6972b5fe97dbcd858b9ee26d3d414dfb06cf7f704dacced6e60b57fe41cf4cd643ec8f22463d455d89133b9240ce8529ee2a5656abdac4e08105e5ba54d9f71e79d0ee464e577ebdd0b0c739998fa0d91cba3e0cb1e79c40485fdeb383f974a59c6c6e432cd1426aad694cb2b4e475a83f11f8562142476f02777946132e848d1daa3d6474dfab3913e76d509fbf2e01bdd959407c84786b0c054ecb8b3acdff89a30bdf67832178c26064e185bed522c0972f31cf4b45a8d71eb62c8a704c42a1723981d3192422130027d01e54f1a135db9ccd3eb9b589eb41b14423f52953e2760e0c83b8b475fe0f6f6f62fae8c72bca35f7726e0edda6ff5380d5cb676176c9db6a4a72ccab7e12d5d78b6a1184d494335b9810c0411bdea7beb9936a0d501d3a11511c137cfca804a98762fa2a88903c92dd50f3a336aab976a8288022c75fff0e2e43b1cd107cf1656a7eb074b65d5585d6168ceefbccd0742ef07777a814b5e53e3544e453b293b2fb9cf8184b22d4b6a7c2b26d9ad58313e3c77c5b2d9f364050cd7f954472710b7f78c67909c32a65a026c20176233f78e7177890ad2a8573f9d61f78730053587facd78f638c2f84d2828dbeb4aabe276be3b7b08c7db295af27267acfa4cfe9324c42f3e959cbd070490c0b4590a19b4b6066846aa0db42f261a7dc181ea3440def2bd239dd80cd81534876cb89315b54201774556b47a91053e237c0bf0e251352ec1e28839907385910af451d65d6089a84ef0d4e7b06e1fc2d469e1e181f952557364aef8f631319ec6e877819a2000252e88067abab9d4bc734cff1dee6abd45d56610f0132d0b0dbf00c1f6e8ec4a169ce4bb8ee1c18cef8af1db2b0606a4e260563320224626996358fbcea343f899ac02936e23f4883589fa2b56e85ce5c2c7a3366b30cf8c106fde14a29992aec04b7dfa935f7d42478b8b37906de33b46b5ea17f02fa69297571125b3093596060b9e08057109c85d4f74e0fb2c94c343a1ba2ca91f539e6aff6d6bc033aa172357d49842d1c2f93e9d3b0ac683f3c156739da6729dd82a0b8be45a4581a5d7eab2e4b07ec4175798e9380c9300a823b125344e2cceb607889ef52588e1db3b53772d752d9217b90c8af26a114558e275f08b9badc82b684b0119b53a222ac4e2181646b0870133b274105e0bad98c0fbd715ee40e01d373b83660717b194f95448c32e343f081cdfc59114961e7a2c69137336ccfcebc548691571631f5e0f373d5fc4e8222026d5f325adf6b4de88a3822837f2c80be7662568988ac162e6df3064815ca46f1029fd0e03c018300d5a61e5ce61e795ba48421b5f4f790ae27de462e4902b8735c6ec97f5eec9658dd33832dcf1b41141eab56c7e7f34d7f3a90e8b07703b84fa06206437f2094c383a2728d0287f36e2c1619ef22c1def3a77b43f3f3e0e0835a7a10d066e05afa9996a3feac2c04d29c995162028c0e9f94754dababe8598497a163292b90db7d0d8467e0d530d69e46ebbaa0bcc0f6503805565f82358506e036db908c5477366dbd7a30dc65b4a1a096266da10de363319615642aa2f4016b0641083166af7ac5ebc58aaa0d0bff9e3b7fc45c9ef6d6e3aba9a9b6f6458265f337740eedaa3135227337e66c50d62b79941b7ec406bcbf6713a9d485fd49d7b238225d356e8d0212fa3f7a232b83ceea8792c110cf348d814960ea75cccffb2fefdcf9d76f6faa20bc36693319a62d9ad49011c7b63a280aa19a5fcf788085e42fa3b2174d7dc3983fed7f6635e6ddc617229f09f7b56b74ecb89e8c5cffaf454f0db8bb39f9e04a90493fc9a5fccf2428584108a3485a8f5e8a3d265e8d9daa26f4c3cd8d4918216f3d94e0ab322d26d4aa0429854f372573d52cecd14784e0a153f1ed271f737c443bf433a2fa5ec7e2b8923ddfa558ad3d9b2259433e16b92219cedc4387fbce2a50d9e0771912e67c69af51415372eca6a2ff0b1c2e283e5a0df54a0488b2abd1724d4dc801ac428af76819e962f68fb45f488d27416667d4700cb693d9569b602564bb8ed304e9f4209646b4647b8552237db98c24373e6aaa54e91843f345a49ceb85f2556038b3b804309f6fef5804050fa74b18392c001dcec750ddb2118c707aaf596bda96624b8c002d6b05e62d1e382dd5dd7b2ca625a67be0c47246af4050b4e19d6fa61dacd8cb94ae85bb970ccd44be923c6f3fc8b0601c96ac33231b4d3fe9620a175d3fa345354df5bce48afb7a0747495a86712839791175a163b6925a62c43bff4f7f331487354f96f2cf146b97a9bafd8ca38fcda97c2f8bea8afa1de90a88032fb97d04a89b23f391e7691c0df1d6c52ef116328ceea202cd927dc677ee2e9e7d3c94525356d97c7f00e8022274a5fac9be1ad0b8b894b9d732f938609512f8893cd9c29cc713296182f8fa0bcdfc5678f83e2d8af80be850fb3996ac6b5b6ed0df396f1506ce6d9a741c3998815c542d8d8fea4b60732ea58e1d638bad92fb1e9b8e36a612c128d76ddeba2d7bb424f7e5e4c25c66ee727dccc089836fd6bdbac9cec36775c70ceccdb2231bc8cb62d88748882e5c51630ba5c56d8f4a8b39d0f84051c7dcf6a9c8a9af816d511d314647357fd68109d3844d5f703886fb9e2c74181e98dfc77f80e78e76a2ca8a3d7c80aefec3338401d8fd4bbd3ac2ecbf96e42c774fa79b59967295a2477363ba108b6aa476cbcd9022a4a7811c0874ef75fd10ce622cb9fb7a9979fec8dcf3b4eeb6d7e8eebdb1b947b760a11fdba3eac3de6108ae31e4702d7acfe525b87ba1c65d68aa1f9867631da102669aacc40f0ffc7d977ced6f7a2ee451212d856366df3fdd5f70c2bb29483b9c0835f4692092a23e07757c609afc8d3f91a1d0785d74cab5af7bb8f019310cf0caa1768bec2e813ed0dd6814454ffe8ff71346e3c1f7203b4d19a3a3e65f54198f567a07bc781dac5d8e3d13419148052559f68cc092a2793e66b1934a70598ba6c56c79e369f25c44f68125c56a55d7142a2dea2174ef61da1135fcc0f268178865b3d186a5d0df07b63a2fb3f57738aff6a4c5b14df74052b3a316a4e7daffa48331bd75a32e95f12cfdd67ce52318ab3a28fae10e5a792752ae5f43669dbda38e4b895dee43a4bea82dd1799ce15e3e2830389a3c5c2f2dfd0c5b7dbdeab9f64a2136f5ee329895c4a1917b9a9ba6567bc8908a1f7a274f10ef5f40e8a302550d1be430d504ca6049995b77661c002be7a4659a829f4c2f58cd5ef3187994ed9de017ed78f8ce589095687ed97a12ba45d86be5bf29e5429064ca191b8b0437736810b7db0dbe18eec5eb117827c7d80686008bbdde39e57e890a8a08ac15168986b670d06a1285c2d47215605c8700a98a48b94919fed61fe2eeb20c8c53d69ea50a8bcbe987aeca930511d9e5872089b9c86734b4c39ad4d256a320e03b0264b0ba0cb8dd728974291ea496328e4dadab8ac687d3e13d833792a971b013d32af79a8f9d404cdb5d17be509df8bae3cb93b097f31c074e466c6091ea64083219cc775ba37f030470bda1a5eff31b3dad62b40c8cffd76754b47defdaf1348d80f21ba4e8393ed7e3288c027c0c60745e77835e04cd9c9fdd82eec42938f11c772cf79a78387f307d1019bf6fc27449017d8c2004df66eb40bfb8ecb1b6c17fca669303428553eb18a9d353331bd9ae140ca9a79dc70b7131c20abde0b145b3a11e53efb463edfb884434981999a83b487835c491ade01f838519f2357e0f1067b23bd3c7bbd7ecc4a94d980e30bc0f6901ff317aba9ec88ac0fe84d2990934181d23ccc544619dc9373cbfd6ff27012b73db6002c1c4c30d32cf2ee3dbb1292a05eb4deaae74c70648c3330753cd5dd9b9eafdd335780e6dd53466d634f1124a71514c06d26a1fde0e375fd0d937f2f81a2e43b7e621e664ae4c336fca682d16386e0a516163fac82ff0a109fedef2d807260d6fb1ae569a030c414192ead1d8662be61f6828307d2a10868d7ba81e4a5fbcba697b669d2e52e2353c75cc1af323f3bd4266b5df07f60d89bed0533bc5eb5771a3fa586f314c6596620cab1d798e691f537d000fc187da32b1a92211d053c18c71d35df02b9af91f44fe1371d76940db62ea5358fdfc60dcae7759fdd85e37bdf1bc7db17496e982b7c81d7ebe7b61e4e4434a968e45898ea0547bac0e08e18c822ea6ed89ee2ef5fa1a0a4b49c39347c41ba9f6ffd0a378addc8e0911c26af5ca861f65ca085dacfe55a413aa5caf7cf9c4a667e918fba0641985ff12230f7a715c2317a0bee446d1f48c1136241116f52d90e5f16d02306571bae96c913f5236c2103b3d8575b8ba76eeef4e5a1fdda1a373a41dc3f52db08a3170193e5464b0a582ca349204f161e38dd720fe79d17aea72b751a96eb1258e1b2b8bd30a24fb2016d9a46407b9bae2415b9bfafd243313ebf05bd7d910b3f3be5a5a273738552be5a656b2f0d951740843c0583731379d06f431a73637f09838906c793844eff1f1502cebd2e086b1243cd8c7069d45c5471f82847a3beb206e6d01ec67c403d791bb6d5c60fa584d9d84dc3e50f4971e7d0dc053af5ceab08587451a720271e267222398836ce49bf75378fee98e80385db75e0e1c935b7f568856c4d659e70b93e78b540207a42d18b10d84d75494a09989706e02877d8434d7e82b1a2271ecd56d89602b2b7db0a31f2b7ac422090010f987c341e42ab41d378a7c9eb5b3ee53a57df6cf97607883ba4162a7d913cc5add465a342a84d6cb86c4d3b1bee994880461d5da12569f55e689a026a4a50947d909d89552e1c36b8dac056a1eff2b705e6b9f7f5efbd494bc1f004f843fdbf4877f6f00e8ecc80f9bd2595fc19091360423a615a19e70ef7ba4d9b31b12e23aa9b9aec8358e32cf9b95282372f606f9590bc225558d0d3db619c4e640ef1ed91db5476f4b157c5ce8533d5c5b094edbc510fbedbedf66a933f5c1f1a1a000f32fa34609a4ae3a92e558681a74b635d5a0ec89c778e8686526bae0c3d9d4324608c8cd1a0e9477c6fa1664a2d25ddf86d08a28d7ca6197d7180b829ccdb022ec59bfd53196fc1e1e40c390043da76cd989c3d9330edf8a24c1f827490fafde7cd3016f1681153af34c12921b1852d7f38d5f452f511059391e65c378880af58ccfb8ee2c5b082bb3f72fa2cc59f3fd083b0b7137bd00313bbb9c3f162b43dffd8b933e753f505e726ffd5f680c75640958050ccbb173dea2d04282f3ffa658223c4c9ddddc27a5d181b63e006a3f10f8a6a87e2f662c812b9db2d0d422d75db10cddda8fda4b96fe782c9ba2900affbadb1920a8f8dea4a3735a24ba3904d93140b2407091f1665049fcbce474ddaf26567b809626352a2bfafe041db92679a2f218b6960b6a64db353c9b294602072441cbc82dc8264b5f261361e040678c731f7799a39331deefeee5f0099422e80df0d2f9e3466819cd1e9e69b6837fc800eb17df82d409e93c1d576a2e281b6a1be03c25030487e9d9809c3ac9dae1ea86fdecd9310f6252f84e2ba3f0ff0d9264bb14bac54ee1b52aba06b8c917fb1e6647293d50aa59709adb5c9153d1d37704fe6d7d1773183b46a02b4fbe8e887e926005df1c08dfa4984b419a68b79d45f25fc00cea7adf077ae759d3bdb2bb74cb99c9697b4c620a9492560ab4b3213c8656588dc84e2f2b6bd0ca553acc8afd7849d2d0d95ce593b98354e1787fa9ff9cf27029e058ce94b8dcee44ef379ce0cc21b29787c99267ae199f09b8b0edb5c8d807538d3ebe1ed75dd5936dbd8f4daf169e87f71978b6e06a203aebb46a4c78b684a2a0cde77c7fc900141bbdeedbb27d18ee16ab796a78cf1250c7bbdb5f7191365cde6d0aa0f8c1d68f6e53f5d8ff31f02d395c7ac3a34522d4f2e4d974c2ee3ce5ae20d8e971ce9bc4effd501309d2ca960eaf9da210c5eacb555e0119888f12f11381fca3bd7ddecbbc0e9800c6f9f066ade6c3b77c9f5644879031eb934db1eae85fccbe720101dd07d5c4462592bff9d6d2364ae2c73358c6d8c643b86a6254b7557728ce07aebd9ab7990d8e6b4b04dd26194ee39300fdcff34f4532a1a049a3029fb7f8f6b66a9be55f89130f166a00d23c27af73fb9c12d194b762313cdfb6c761566d621b8793e293f15beb4eb94b07fbd038045846252fe0e8d4432be8096d186a8397b22ae3a9470c8b9ca89946225f8f71b5f767d3a664f9036d317ab94d63c78ef7620f0734bb46814163dcd0ba1fbff9c818bac4dd930801137455c0fcceeb3335819107e0c2c533bba44f791a5c84569f7132697727a13192b07155aab679300bfa683acbbdda05cfb78354a24cd8321a64afa6c122861b40b5f1941484efc26c561201d8ef6de0d5dd0d6d33e984dc73c5fdbc5063941cacd583d169c85ee78e362a2c150a04936d9c8cd4399fe828bc39c6a0236b9be89d8f6315fd28f3b4373bacb69f38e2770b1c44adb4bbc1271735ed161d66fcc24c39a26844ad5aa6a2ec26545ae3146d4b82e761710e039cfbc5cf43bdaf519f737ffe43c7e2ac8d056be1ede76ca7bca762b2bdb76af06104e8af1aeb6eae30e86228b6d9b50094d036942c8c8bc03833199805348fb9e0b1ff573564fd69ea4af068aacea3414614abe095b9481606dab5033c7835d55e0de41f845e26a2f93a941b29d69b4017b3524d10bf50a08bd654edad000cb5ec14e9d1eaabdb9d6ab646a9162ffbee865d3c95bb7865ab1af462e24dfb82751420b25557d29fe9d66d1611d992f7aa50ff11a1380ef5368a2ece2f7b437505ea72a10320950d95ed6905e43beeec8fa4a65f51cf2be6f7e4e62c1c41e1a2357c0d78f33dd651de15b69b445f22077704c25a0551b1c4982b5403e5868aa0758d61c72f184a239362d8609bc20b1668a3d3cc710936dda8190d77ac897413cce583b4301d88fbd86bba669cdfdc9717d48c995c69143bf4214346d533d795b2c08c43e78175986a4c5e28154fd8615e58de0d38bce4fd9ffcc76d1ede5dac03bb3b705d672f940980b25be633a248b50e5e43119b37c927bd7c851086d09cdf7a5662dd6a98067a7a8f7cd414530c821500461e40632b6e59713c2af93ebe5296affb63af422ecb79c2cebc09b81637ddf2f7f959d86f7bcdeacd751f2c1305efc63886ce533255db631013545eedcf256cd17ea4cc2beb2aaca74f08094559bf3cf419e47ba51e659b6dd02cb03126b10ae4edaefc690408f5afedd30355845214e18dd1573de941891f9c87af8f5c9ade6dd5882c23dfe015f27eceb9293d5420be38d5be0a80adf516ea9b3d10baebef10eb44157aae4a25de7301b4481365a9ca1c377ce04182006041881e47e90ea7eab77f4fb0b117c7ed57cc92f2bdccf6a51c3875a9e920e25f4b0129786fd3eaafa64aff6abdf88e8eabee5808e68792ff88d5b058717437fba5b22b27e9c55acf8d9c6beedf01bbe73b40421532f7c557a80dccc3e28f12036c7d9d1182068805ad700c9ed097cfedd8391a5d057a1d42786c3ef3e0cfad6d170c47a6641901677c2ee076ec651f1ed5f5a1f01a5b96ff6e0c6e8cf03de2ba9d1ec238b0a162c4360f79c2da304d819c1e221ed36ae32cc06e67569a1a2f19d96cfc3df9dc03f46c2798a0e1824e9e8b7c74f2e1b1e61f07886f8400891a39fe6f56f4c21440d753aca8b0e50dcf693c01f7f78773342eb13cc1fcfeb04980a3befd42cc0918796b3df3d27d29e99e3c94523b03d7428e3aac8548538206940bfe98afc48dd7f75d75c529d21be4dbbbe48b2d8e42d5fb0f89b97b7133f33ea89dff6cef52dd13f8bc5f7934c6aff2b24f7d6e3cf407e865686783016a637e29f8cec2ac7d71ffd78c5730fc3b9da38a9aa6a60d3354e95470cf2954f82c6234cd74b2f2ac00a9d5f70ae87207be7acd400ca1a43e24cbbf287a0f79e25c5bb4f3a8b86163a460dc9594aa4daaef486f606f2360561e2a2fef764c0ff94542536701ca27c051a1f264eb5a7ba47b77e06e2ed32fc104c2ff03da0e954c63c1a1410c769a70596995aad3b4948e76639c04558579524e5f2259b21f51b37022b0e15502705d047dc8078c18e1ce75000447925ee58794cd539f5569bf89a6d27796523b637cb93d7c57b636e0830f80bdb57e54f23e201cd2cbe9a5416b75e030b693193313308abffd1a0aced19bb0fe7ce6c7401ab89c39e9e1f6ee7a57e9926cefbbe6305ad5032d11850f158543925bd8ddcb06c86167f40d7943b945e783ae25db6dc01d5e21eb5e8b34ca7f3ee43eb9e559bb6107e2b4afd5621ed6893c5d4f92d8813ceb85f993508920f94b0eb9e8f6382cd87f379a46e0add26de465a49e5f4782163144e20c50ebf9664d2917e7e738ff590a10b336bed23b942f0609af8a63d2af0cf24975677ed7b6e9b9596f44e1c7bc6e9c2e151b9508c70bf48d4569b386c21c929746587826e3315d9fbe75b4f270f377ff5a758c337cda6b952d84add63cf898550f35f65cf368d76a2b0c81ff3a34430900d8d5d7180bfe7ab33e61743fa7b6c3003d38c77ab821f89cc9d54ac51d16ecc6fe511e3837c7227d0eb592879aae31ff2eef041a5873ed27d8f6b444865ad095df559361833844684323c8ccf24ab032eefcc7cf97d40d085af471d797326a85bd9d7f15df57af543b3ebed32e1f4cc9b0af1dc0010f2f6bb059b08c308c56973af0f8df0f6f903081ecc0507e08ccbdd974fd909e0937e3675296b4d7843c345ae2e06ebca3de27daaee9686f65ed2d6c7f76cad3433989a5446f7f77a778b226a34efdc7d7a716b828625e8a02946a1375391fd33e98e060b2dfb31db604d94a226602ab2dc868ef0dfbcfb4e1aca4a8c981824cc7bf5876ef4d986e3cd8c0db5d53a44c2ec00ceba6662c27d71cf8a4419bcd46016f57ba771da0fa24dbd179dd6722cd41ac991dc1357fe56ac9ff889e4bdd10dba3db13e5ae2126f7e19b41f3572ae9267a00999ed0d0c78a7bec302afbf9b9ca1775236c9cfa2078e9bdf5d7b40d5bf6f84d386a6b20b12ce1c50c6c6480d3529ba7808d9cf0b74143024030c05a811c60b4abecf9296da9759cc303dc30cdb536ac669a6c6b68b2755e882e976f82320e42a0eb6bc75c38bc0986bec95351d5d48eda466ae2de3ee1bfead078774f1893c6d23e1f1a9aca98ab7e27c3b47473438a2493f0b94433669dad94d9c71afffa1e20874b2bb789df16cc7664ab005f21e127f1607ab6afcc36e7623f443b8789dde73fe7ccff61e8783fb7e8e7ae4f16d59451a5da0976f94ccb19c871fc4b34109c514c042ba9312db1a7f4ae77ac07ab558196b94154573d99eb8662b5ad67afeca1f97ebfec499f1c33b6e07c65b3a8f7f2505079513ab9db692ee07b49310399ba8e46c4a52d7fb768f8caa90732eda72af1a4ba5df5376f659fb7df855eda65d1dddf70ade8b0764b05cbc88421d09d6c809cb894f3741ff9ce3df7dc5234f6c5867d39fdee8fb827d61b6b076321de512b41354d92a749bbfe34070f9ab4911304cd3bdc456f70b96b64462d9baf5609d022264fb40fcb781b5edd0fa0942949305eda354d62916f9dd2dc168d842e86ec77a04aa2c65f96cd249ecfffedd7ac8498175d3407289bbd3a816b31005cb422f0c32d5c563352949aca0350ea9b461ec240256abd96e42848a4f4ea54cecc8a16b0418140d68e5e5bd31744a93a5842ba5d6546a23265f9517bdf09808d5cac11ee24961ff70bcec19da42c2cfaa221fa60cff82dd9c8e1c7f84a91b4b14d8d5fb2ebde7d81ec5898412abc09bd9dc686f3d5098e491f6c52b5ce16508f71b2b02d2181635f8b4fd07747b2a75c835b007b36e1f92a229dcf735b0723e0c9cf8cd02e5633c790af1cdc6b5d20302e72357b7788341a73505e1da91edd78f3eafd1accb20ca327f9a6ad0bde2f2db4f9156c1f798884805fb4b496d491d0a5bf44bd59f20a6c0f824cb705ddb78119d65f7e832430499244fff021026f4bc39b47204d42ccb9f75040002820abd79bafb7d644efcd8fad02fe94089bc99d54026998cdf257aa1d69a1411edd11eeb9d01c963a3a37e2febad6d366c334a920df474e7474cf5da6441c777118b00f563ed5b60f4e39f499c5c455c9694abb4d41706b181f9a734273a6f0661b32c75059f7c68aee20b9b6261716fcbe47dc8e1436e52ccb2048c4a038db9f0dc6aea900111ec7fc4d68706a1e3d7dd4af269749da9cbfc1dcaba74d3cb019509f90982caeb403cb67f84e212d2c5303690386ce814826f5ee70f25683c7bb70beced0893651e7a229c97d96a6304b1ca69785cabb5dd664ab9711e72d6144117163fc35864c785e898143392bf0e01bdbf55dda14d5e40f5236d23cf84a28c6a53fc8b67343310cc7a06d2baa2efc23de90cb1e2330f5916ed00ff666d78fe7a615f21846fea6ce843774c84698de49b2ce45667d9e9fca4c929e67bd7866daccc513917714e33e6d04aadc5f74acde0be15dd13284b5f51c14d814ad05793ff1160067bf72ea17a92f68c71bff83e0886987ea624b5edbadedd6136f4e203feb6f7f09144f6506e8b9d2e236c09c0846b1e612f2acacc3a1cfd7791aca45a6bcf4231881568e733fb5e7cb71e21a0a6d6d14b89ec0371ee80661c0137f2cb863ce704df5513937b93c37934602faa533b7b6aa4420386d90f35893a4a2cd76a2c5cd33fe31f5041670ef9cd6176936c723b51f2789b3527db78fb1e8eef77c6c620acb51f67f319d820c929585d2a0c7bef271241d17c2b8232f512ed9ebaacb1ec41864f5f44bb4649f34852a1a8df84c1bc2f8a0c1b915dd309e5fe16de461d661aeb524ea3613079cb3acfb2a327bf90214435e58f6ec4b4ce9febef382d43ed0dfd6240eb16696ff481aaf6319f411319854c3d088ad9928e76cf82a526709f17607f5f1ddd1bc53cc54bbb7f1a504e642ff85b293428304cb9390795df08c7b018e63d027bdbbf77d2d630f61bea879dfeab50cb9e96b84e3b94310daaced23253b4e259cf0008c089e7239d521757bdaaec1aea4bec9c90acface804cdefa805c52e707f1c6009aa039fa849db088fdb030561c4acde220567a0ae3b000954a9a68c544713f20b61c21e20cb6dc3a7cac04d46ec06d63d5dcd7efb02bcd94302fbf786b6c134799e28455eca75feb2cb7a106f5be460c297edc0a4ef078bc795b1c6d5066400b34fa15282305486a0b54b484e35faa3a1571ef836bbc9a042b96b5acbb44d976f72eb9fc01c0ec1ac15a28e39a167bb23ef3b67d04cababf7dd4be780cc156245e44d6b64a7539107eeb22ab9ded39f7d6170f0d2cb83c809ad5704677fb78b7d0508d25d12dad26e4ba2b30db91d2efff7d7885162766121ed1f2b20e759f8cb1160060a631c32f4aa1f64db0f2a7aa983f6ebfde92f6dae430ea6bec985894cd23d5339f04141c00612713514b02d0d1469018f3ba630096f91a5e8ff91b6f083b527d62e7c064cfbe0e92526937db74c21d6f2954a66a811513c38e3b5b3358575480db917bfea3d9d1584fc664ef76b912d67880f35855aa38a7a34ff1be72997582eaaace72b8b7b04e541201c88064807a82cc1ab4daf7fe4bda0da9943a4460d95fbf6414830a4a37e5513a6eb2685f96d14b14527c28c964b3083de6a81f03a66708180d809d8a2205e052756d60a71584ee4573c03af0abdec3daa02fd2cc9f0d5cf60384caada868c804f5f9421fa8981e17f86c6ade345c9d60e2eb190ee32afa7f769b390547d291f845a06f2decfda273859b10567d1c2b86e817cfd730ff40c68bd3fa5aa1d11b465714fe22de5e9fe216336c75aed5844f24db6532dcc21d3dff07990f9e1c952f3c435610a8bd979a052dbeaee2f0d091092af5b963f4cdeadb300df25987151c96ac311bb35a57a7e551fd377ece123cc4a7ef61235de1636a6f9ab1a46dddeeccbe1cda489312ecf9517cccd75838e62bfcdaa9a50acb5cd90ecffbf22e83f7e6b6b57c7745e95351968e733d85a921773ca879e8af7ea0e171f987fe5b4461e8a6bb69e33e986a92167590ed1feb342be2ea0df5e150e95f3d5ce5d3edf86878ff2c9fc988170786bfd6fd500673ffaffc6f6dcc0fb5869aec5b3f901e7efe063bff154f92415c1c7e1e6dcde278fac9e2b10e167725de53ab1fa4011317a547715c459c5dfb3918d3a784a3d143978d85a4573628c0a11e27ef0f88d029283a7e48094b06757b4f7c655d9ba7792143de227b1d31227388ad68d0ba62d22ed56ae2e8df7b18a21204d585575f5a644a13c6a10d4893ea7fa626d4259ab63a6b6d04b8011e97fbe1a81eaf41893ddb25268af80e86b5c83f7d906336acff833b18b74be5de3ddc61096fd273f9a8d97bf227fb22a0794fe53e6435e752b1bf68804b3cac543ebb66179303dcd0ecfe6c94aff8a6793835a34bb0df7d88f1171311f18c735425726eba83cb573503f5f6c28568199550c4f6cd46b2e05f2c56c5a2d17eb1398c2cbf6ac7ae94ba8b1a559baf830fec2a590a553f9d2a5ca5e758fe5262257dafcfb436157e4262e0606f8dcc887cac689cd675a31c10b628047eacec1d64a02626609895536a575b0d9a0977a119da8074b18ea20223464773cfce9a490416d87a336c05e00c3d281b9458bf0057bddf5385f41ffcd103f813ecde3fd37f64c13abab65caca3fba431cade7383803efb7829eb8eff2f2e385704756cc7d18ab7c3a7d1043a7e06fa6dd98c58ba5c25df2667d1b5e419720cc085af4a2e937f7d5f4aad4ebac6d14ef9d8a4154ea371d1af85f22f71ca3faca5b626dcb558b554034d735c26ab5bf6a6207bde111ca02c88ba70af22e68c1b597683d54c20bfb289ca1fe80e25d1329eaf14e066caf5b02f9a6579c88f22a5f5efab922965bbc4cb7ae550cd36ce43e0dce8f91692b8c6c8b590a21667f98a0b54128c1ab865301d346fc1d04ff671e5dc7534e4175c581bb4f5f992eaa3afc7e9382d9b4f757d57833948067b9390c9fbd16c320ca4a35f6c041e989f8fbe75682592bef4de82de664a1ec4da4d02c98119de2950dc3dc3de25130bdd9674cdd5e8fbdfcd6e6d8500bb1a0f6e8ded670088ecb55738158772b9375729c2a3b31dcc2640dc5db35d371535ebc227999a9fd07fc2e32823c22144d07be8f0731d93b4ef197014a5a603e53d51ad7a83caa8885154a9b56383211c91efcb81b7295613fe5be74154f04bdeeb3d00f4d64a553d49edbdd6799246863ee205856437964ee2f76374d41859a98324a8c4822d8b8cf8f0775fa558c7f86a3b2187124d97453e29a0c80c6cb1c3204ec09deb1231ef0154afee19e50f7a35762e7cdfe39ad0753678d18f3502e7aab823bb6f82e591f70f61576a3e142899873eaa6be8482ec35457ed7421720ca17cd9391fda8fe2d3ddfdcb8db6884c0b9877718e20ecfc7dcd2efdbc29caedfc248efef041bad62086704f11ad566560ae5a1ff8aac6abfcbb260ee867ef2506af6472c82c6fa21beaae1c45680756063893b3861998b2c077af2f624f6f0d6a2a1e4d9945ac441508fcfadef6ee9bd0e3254ecec003c329b994ccd24bcfc0c20c88b97f5857671c781721dd2b881441fab21cd71925808c6d07b53fcdcc5a8c4824fd43ef67af131c8993a7110bb069cb5cdfbc672495980c9c9957fd0be6fb50aa415d5a79933d439e20b71e5c0a99d7966dead74cfbc416be53ffcd1b567ed0386273b01d8b166a28e580d05442bd604bba239984b40e1c096b9643767759f8e7d00c719c68e6bdfb91e7372c9fb1d7bf65625e8cee5b74dc7d7f35e8c060a2a70059b40b5a321a51bbc136320ab09b4045e7124efa67e65ca6a3119f0019b9f97ef31260890d42eb2e719b3f541b1f45e70ce90a60b5ceeb4ec98d2991eb0a69d69de311ab0df08623991b683e6caa6a6d588966a12af712daa705d83ab3a3463bc07eca9375841ee93373fba274f7dd83c201b824cb5c7ecc031f96b134cd24eb8907bcaf5d33f4dc715fd9b84f9df800fc6fd697d3cb8b9c2af1c5c209b1ae4d9fc582b39e67f1fd018e9099f0e04d52aa510758eb9ae7a026a8b4c63749dc03ee4f9cc0c46fe60e8a600b7db104ad529f1ae27b8d3ec684f7be7143cfafeec9bfaa31cb1b79f49345784095454d8e4cd70b05bf97998304c4fbd05b81ff04e2893c30bd36ebb83553c930fbe474745ebaf1168719f87ca7f1d768a06e89d2f9c9dba2ac7cf30a2f2d70483f79a4d5319b3a0f5ab82047a5bb9b47c5fcf5531ecbf3c969e64477f75949ec347c9902dadd099a9eff88a6d6e73083ed0a7d81eec2fef30f7be8e829df3805c96a9f2107905dd3d06793ffc4d014e6b307b6549bb6791b32040a697c4de549d9081abf33dce88b419bc38556537360d20e77d527eba9c989ea94dbd4e9bbd2fc53225eac173f9c2b2d9a58c0f2e6fa655e7369fdc15aa0656fba1475e8f9817788b9237178be3e1196d0aea0cead9a2d37460035d3b6c2e7de2af140f79db1004f2142fc2bee2f3b8e1fc86daf430eeaea844a2e662517a2c526c146f83cd851a57d6651f253d152f413922cd7113776d6fe3ed39004470b2a8cf9e0d28dbfee7a0c7b735f9e16acf955c67fe6f50b364927d42a72449548b1d695d56326a7c78d357f00335495acd83a20b7ad82a0d5be5ccf69935924f36ba38eca0a9187b81ec0405474126b611dcf6b7e5ff34af6be3c071033526fdfc7c24ee1318d5f89abc3795e1f73a509db3c3c42b2a8fbc3ca4000811e98595aa470290e5ba452ad4cc7b1591039792292a82ea07f1efef1ec84b44d36636b29c12426487854e109c4acb2f19a86d817cc9d4ddbbf2d5527f24e56389ae99856cd01d7545b89a2cb4f513a456a8a32b231a000ce283533889c3daa5b9c1da34c100522abdd696c6b449b83a7e43282391335037bdf68c2703500dbf819c042ddac8637f5c33facd7194f3b49a39b922fec111754da310c09b44283b7bd68f8cd9d43e334f93805000e270c6082093223bfd5b8ca61bd0009293f768b2fb101d077ff85c06834589e390569b09e4bb5e63f1062e418fb0abd60a9e04a09c265cad07db856dc507261ee680e7e015d6e84a1092f0ef4d7c759290213ed528a5165a26bb4974397ced411e2f28c75c8a0dfccba31ee47cb928490173f770b9c66cb5b24140735fb42df418350d5a2152beaab78a1712475696cad0fbf5defd46344025d6d4ab9974742833a67046b8bd88028a45895e48e04561763c9eea2dc7cfb94a8a86a6255bbe186b54819616f1d7a696c0f6a331b0f063cfb15e1b19d0d51beaf38a21f26cbec608c0f61792a4c899203200592bb2cf0056b247c8fd4b5b8bd87faa59c158aa97e87eb083987db090105e4b85c9238f4d65bf5d1f886ffa3789bc9f886f23c95d3c4947b5b0263ae2bcc3281321f13c42495006340c6a266e00509d6b8a9a4ac7967e807f36dcf7377284465bb4c0ebebc09b134359d1e53b45a65c8d152e418a94f953f064d08819e217ae503060a4e4308fd941dab45943081b04a1c10117a30d797a50868ed3aca904a211a0b4db2644089435e2fbabe806286dde852946140b317a412696cc4507772f6cddd46dadbc5232070e8c6b3b7ea54073699c0687267ec36f1f804cfa18863d7fe3120187496d654be23e93dcbb5ac5b16cda9a5584218c9f65fa1c35ac10d8f43c977f251ac227d4d459d8f0e529954e32a4eb6ec97068ca2051b7febb7d6fb6e7366354e39e93395dc9d05a850fc4829d764570c95755712d37161fe2cad9f1576f0a4422e266112631698bf21e1183c7510f336e6af0f48cf0acb1c458f71008bcbfffa97af4f2c354ee67ecb4b7da98f156a338fc2ac1014f4c9597dcc6e18580c434b5065dc92ea54417fb9c0c9990f1f4ca3a9c6e525c214b71cfe3cc1beb142527699f4761f384f985ca75138bcec7d661f1b0430d01e4779979258cefd8e6a0698925bb84a41d5c72447d1ffc8ea810341c6f78a46b30be78fa3165d376424c94a0d71a6fd37e04b5df7ee410d45690eda0bbf60cafe30a9cce718ac7c432167cae7df8ad62e707fe69bc643b78de154fa0fb15e12d1c432b644358545dcbb7e7c4e926affdf13a7adc17e633e24e94fb3924b75bd77367423428a56bd832918437f89a98dae71eb7b4d5d28d785e624ca7f8b8f0e18c51e152b1162905f2c0d512c8c0a778d8d9f0bdc2e69910834b01dbc0695b936859907bf74d04718928ddfd27eca47e9e334109787b3f6931d66206f7cdf927a257a80ed968cd545fcf04073ed7101e2d75070aaca25ddb299cb370a3a0b9c129d30ebf01488e82ca93e35a498d74cd9893c72a7b471ebeb48a4ba8acdb9f8799c3be5a7ce7fe72b25a077fd3cfc7e617a6de3fdaf518507f05b208c78e9dfffed2b6373f6c7b60e03fc3d5a6157db29b8e9db19ec96f3a882fc27d625b34bb76497eddf96671dac69c201bbf29b319d29dbc160c7930c3480853dc03485b9d38d4fad11d530b5ac569ee75abeeb1c555f275545ffe55d819edf8c5263077f3ca51c796923f914b8c4cff8600b852ccfd95bfe1965829bd2ea5c45ecf09a7dbaf51f6bf21183a430750ba06f4a90e2d4f374ffd7379486fcf0304dbaf8a5fffb859bdcb67dfbef552ee8b5f6211dd0896ee0beb23304edc2c7e43177fac07504296344e51f5aacfa24d56124814ac03532c96dfd559350203d39c839826e35a55672e2ae169bf1c52f446f13afc0a6fa27bbed897adf7fbacfb977c9c2f51a0a30d843ae10535e32a99ed7de47ade1eb9b7af2072ca1a7c23b50a0f0362a1ef1a9235ad091944f5bd5df6914d2b01d38a2201ee908d3ddcb3febaf1308c20c50a1ab696d22b09332b8ed391b42557121985b48fde5fb86f46b17ab670c6ea59bf6f9c966cda7319774c552d713a7a1df4398ebcc07f086d4801555c774fee090f7890d8481e5ac0fbd6f7a6d6b20cd37984601515302a7179adcef6a8e90441212575a8d6b2797dd63e2bc55dc46a12fb464ced7b662032a2bac0e34b0d6f6f260111d650c585f654033d660ed8666dd1f96864012e531a5171739e7532f360781b7c56c37516ac457e58aad55aff7cf3bfb0d78f4244cf8df5a85a4b1aa50a84ee1d5b4c3edd7b54dd4a6ffb85963adf153594324ee9d533c6dec6dada93717d17a958344865094e6959cd81c89589bbac283cc353e4952381e188295b73a3587f86ae4938e9c92131bba4c755c1c6489ac52c6a2639cb429cec5d003e41eca66fa31b90d91a2782bf3172f313e1fa19af6914fd5d6e5d1540ca9a52d0ec00edb9d04292785e24f4386f698c75d164e286725bcbeb84452a109eed8bf31f27eda2d0ec78730a5428277a83cb429f1d171c31488b4ea783bfe28f86a63124118ca5b7b1e0fe4e5f35c19390647e6bfad992542b32707758fc0072fd2325c5db0dc21bdcae6dad00ae1bdae653983c031a7d73e797a683f97ed8a6429377af57288d5bcd2e75542853d75d48cb32102199173e84d3f60efeaf1094f7fe684a262d22e33057ed43d4f889c278a60fd8efa46ef9da6d1d2c3fc814cb09bc7f62fb4a1092cf5b4f6819cbef74b7fbb51eb6f32f79c9a6c2b498a5767e9ce692e4a27aadeeee6165787b6c6fc0f0470227ff00e171c56ef4a06e23409e836d38becbde67b10efda3cf91f1fc6477cc9799fa3872709fd20362e2d5a6aaf4622c5e89b9ecd50747b670b2e8ca53c13e3981d86ecc6d80669a752bc72755da4154ab7d760593ad10a5c590ed35e240a9e054c6011306cd57a5d4343e3ea5f694b129b51fd92f09b1341c56ac655fd947a999538504ea05ea003d3e33ecf6a1b9b5ea1052c387bbea25e9709d7bc7ba504ee55015ca670eabedd0dced9412c54be782fee1faa1b6590de377df90fc70ebbc9e4106bb408b45f9764d8c30d60f8cab35772a8a33afe37fd539b371fe84f5a2dc9ba554b12a56e9d60d11b99e9f35015e086249e9f8fb66b124711d964db744af0cfa7d0ca7efc20edabd135e4f278312c0f26b1898dd0d02939103ce4266a65c1d7c32651443d7eb3854cbd2b2fdf8e1f1bd84f6cf82627cb1d2d71e9d7a4899c889faa3be7efc9476fd8b4e6397955c0bce002c299f8a0da8a05765ac9b7c87c01e6035a533d1f524708f7242a51b3785ece0ea66d36fa173473133fb7b86b2bed6b5383c911b5f24cc961b7aee87fe79a7cc089c8ccf714e47ded25d1ef3b8313027dbd14910441c0303318bdda8a857c1ea5bdb66fa1d7ab5fc92b5a49c3607a6bf20d0f63f9b4ae65b9b3c376498c4d106d674f2cfeb2ab2a6a44262072cbe85a01a43e0ecbb3545e42f55bb5b5940fe4c09449ec45103a23e5bbaf49f9d023f04dd656589b5a774727712345158afa87d02660fac435a4b91b8d01aa51c36ebc634a7686999d7864a003f7d99fe57699eeb66c7dbc4ceec7ed1dc4525bcc4ea4bdadcd5bc34d6256845315a9351e238b0435e799d2efbc257bf90d7b3484751fab6615af52850070980148d31029605dfcd7760f5dfb4879d17e6cdfe9f24e62814510b42cd1bf57f8b8855589067bd2e14109804e207003838803614f47791ccbd6cc8a61194d1c4a78efefae651398beb1a1628bfd7756dcc061bc7f4591a2b845acb1c3bd8aedfb206381e1e654a0a539f6031f2086b7ec10425c6be9db188526f87cc59631c49c4ae240bcf69560075f60c82c84b297b324129673181442fe4cc8b7caa5190473e68861dc56d4c59ef0527de30d8b2bcd7e52f8c0a7846ed7b0b8136651d502e8e1e4d25bb7230443e126022d09949d80b23b87f482d0612150ef31b204d42407592ae4a4f1a801c5c46608213ee38235c3329f8e2af8b78b3b74954084f3590a598ed2f566a6da4ed1ddfa2850a7c202f37e7d91da4059fb1a3d87ae825fb6ccc1194b95a84571e2a5d6645df8d19d1d903a0c2ac5720d6986b21be012f0adf67932d329bcff62d199cc97cb5fc5a33d38f3744ae72bc07670b84afc5e06042ddd1877338ce5d25f4c01c0c118f5c998edb64018f0accf5ae4516eb1ddd96fdaac97add7e279cbc1dd2a7b64597535c512e26fabaccb15c97049e1c08e6de31bd52dfb4bdf9de9ecaa7176ee7a7e6bf14cda0e31ecd4f62eb4e848e5b6a4750d61c9acc9abf99fc6c51c5cfb713030b574462757e2f906e6650b40a41f08f94965f0eb4faff18c62da5748dac0caeaefc183ba26fee346a389552589f88aec9dce771f6f4efd1c56709f752822562caab68f9c34ebd481836cc40f0fb56aa8cef6f28bada2914e28054e350fc45eac2d1a3289870697886c9a10df8203f98e34f5954f7c43a4e18b1cc0c1d304a8804a264d3753fdd036c548cfc3d2c81d0eae25cb8343f66fadedf8c2fa389cf1c521bb12b6a8aa8c8ef68c32098319331e5bf13baa02bbe39d4daf15ac81746899395e8355a69d1f3fc65951b53e3565b023e4ce276f0febdd424143a3ea492995eeebadace7afb185ab55afc38625a3de3f4c91293d88cbed1f8061cb7bf4ce36584fb8a55fbbe69721f54b4d4ef1504b6f23513b73899616fa0d0fc7d1387c38c6ef845b37835fb28aee1d51a6eee35df33158f141be7ef09926e38c2fb89854251d0998d723ba7e9f4dd1792e9a20dc5228fa9a744459f17f99a7a950594a481cecfbc08bf1b5da8b8c40cec6f81138019006e34d6461a2aeba1ca42ad161d65965e6dfa170e71c21f2c396e24dbfb697575ebbadfca6b77e5381f718dc0ea551c3d1cdfe4a3aa8531db1d6982648d4f75130d65a2c0bf4eda62b5be18bcea244608f74f8f6d570ae2d75dd73af271e5eadfaf47ea769237804297ceff0216d5b17007dd687d5faa01c4796ef0e25bf99db5bbb62b5d7c5981bfbef6ce3156039a5498bddeb0d8389f9eb16ada14d26a7a162bc014f9bab63fc365e5fe14b97445cf92ed2fe834747d3746795e9b2b9f2c2c40285ddc50ecfc3ef44bab1d33a858aad369835d857fce80d32dc753b367108a6375a651b6993f36f68fc5e898ea99ee7a8a2f748048d8019df221546c0301cdbcb3a0b7e429ea6b023e0878a75f9460294cdd4331ac456985288f85ee4c57688d49ed6a0b2348baa2ce26cec035f536820892e541e9dd2a445af6e15ee3c1ddb7cc9c9cfba1722b12003d4c2c16dea32fc35e3863e9e462c06c69f202d0d3fec4a6160c741f194a809df582db57ad18e9a9f7a7e122b652979bc66ea174160bf922e62c010ff0fd678185668504b35c346617b8833be9fcd20c8c044797b38d8b89b9a3c67f84e904d34d65e4cf202035a25ea2adacc6f1cde0228104d037faf917a3bb047b1f8f75b8f2242219e6981c1c8b07b7329a2c2d2d13b3c407420c62d518404d19d9109441be7c65353a75a5a85171a80b2407ade6bb1fc9bf4de4497cc4bca31c24967ed0ee261635d8bcf89e7aeac9c828b590af46420b653f93578b8777d217a7e0fc8e20358ee49f8db46e4b75a207711e10c074e4ed8f71337e2c4bc3b1345efe1e1f54f30be26cd30accc02486b9d249c3c6fcf04b7d285495d0bb84b76502f20aadb3e9d21cd2f5123f51d81ee13ec5c5c7372e9ed9f95d931c0ee0b10e8582627fbeec295226a9522ed8ebc67eceef3c9c272b9ca5c3f4dc546a1abddaae9aacca5ecb019a7428196c035c258b41802175cd3ba95ec9f544458b2ffe85b034f0f097d9d7f64891d6db2f1d0dcb28c4a6028079bd04e363c0f524f67297681dc8a51680237ee77d26416d310e8f7cc6996e6e99aa149150a7816c16ad419ed15b06c2b7774ea24e1e263d9dae6428f5118cc5855d0078c7f6d70ca0a6ece198c1537f1ab5a69af4f93201faa87434d92d763a6a5815197fb1b04c6aec211f59eec749998d2a8485d2db1155614f78a552527b167e14fc16e2e77b752084b04726096b0b66ed1d6480d29a018a08563059b0ae41af9237ffd53e66f6296508e21aa9366c30d655311bea2a0fae855ab80fb2a891092d49a54b9ae78f0c9f71f9d0e9721e0deef8ebb596361f9fa7f5a7f97d753bb93d23cfd2cee32b36c78c31eea36effbef5b042b6aa99c82b83fe02307af90ac486dbef0b36311c77ddce052cb7329b3d39ab28d629dd82117ff97e9dd3851726ed6a62ea44413c6306c35e88a295724983cae1b4815bc96aa869ae80d06ffa18afb4bf82672a3c27ed4d769e621ca738f554fc43a56c7714a1c7a044c461028bcecc70c4d3aabd9d4f437f3ace00caedeac794a1a494015245e7698b0865be190d5d4a0b3935add457bb727a98bf375db5ff2662af3e02762860684215de16c854fafa67bdc46ba938aac253e22fbd8e6f8595666cc2ec902e86ebfb67163dd64aee30fb73e1b51f4537b9fc1b4c1827ad5d392f78edcab73a03c9c80454fa2a01c0a0520f9686968e6e3b58e85842ca274f3cdb36a7a90e31241b04eb60b0f8c16862ba6c388c646297f6f62bd97392b249b5e9745564235f8a1e8038ee9e51b9ee8f374e8226597a10d36328317ea4dd4b2366bcdc764a797f046c554243f755a72f5c9095a70dd3604a787a8db6b8d456c50644702e62e8dde2f2d444a0c76f629b047f6f6cc1d5a805f52fa8310b27e59563512b0f92dbb074aeb1b9ebea20187d0469516545a475cecab823cc2b433482589c129458cfa5e1b081301758e42613946844883c7d681b21c735f00ace5837e76d7414ca6288971891223f2bd41aebea7f74e325459d4ff78434a69f832b4c4e542348be6089f9a1086127c6f196f0b545672f5d3b2b40e9f0dba236ef9c9794bf4af3720473e3998ebffbc7e5cb87f7d0a71a74cfaafbd973a03b3fd840769602f4b604dc8e16163bb790bea635d90d35c4dd224c23e4d204b1985077190677e9ba3fe8d09d2754ebb40008ada49818bb7a8076778b6495bafd58758a040a233c0c056aa1ee8ec8fcacdf4474d3c6ccaf6514eb2972ff4642af9581d528e6b84391a5a8390f30045b675f6647b40ccf3f36f904cbe5d149fe3f815094c465a5f9e899b2dd0e543943fa7e049bce8bbb9b0d02ab166c0a43a1781ffa2368823911fbb1b7cfe9910613df8a99521e5c6d41733c51e1837aadc4e84d937bfad40a6b49403cab2eba9f146ae629f76195bcfbdf393ce2195fc94c9b99a56be9d466e8bb5e70905d139f9a2a80221a0ab685997a1a0acda20948ad0a32a6ac42d94818e1335daab254611f4ad97add7498221dfdf889905f899dd18285baff3d07b18be78b2e6a33f326307c6ee3bf9f81cc6e66b31497382fd718cfb6e7db5e57b7ab2bc3a3a647f339fda3c0463e2eb73ca252eb15c72df8a826d0c8acd24fd1f43b8a7ea96d927fa3a31594abab74177978bf0e46a8d9ccf5fe2148680b94ef1fed05da97466ff39391acb17c944e0c640d4335d1c12e75804a2d359acbfd95ae25f5b4cb49bf21a2281b3da141a1aa588797891cf8cbbd5c892a712ab533688a47601f17e61ba8aff8214975b844231a74cd29f009a0fbc236dae32a1cf159ff842faf83d573bddb822488126b53175105ee8d60d893ddd87594507a5ec8570c43f15bc0f14412e871a49ce07b3abf4a5ab720605b43eded532d77555b0d943c84ef6093803a479a9443b0295052c2b455c7bc4f203757bfb6ec5faf0997eee9c8bf0380d8d0eb2ae41330c54d1b9a3a2ac5ca1600263dadcc60d1bad27a44224c41045d2d6b759a2d8f4870400283264186f49cfe4b47c8fa117783896f27d948cf8d312e91f97453a8a424009f7dd694c2e8da3a9bf52038ed4da30d442e6a3b14edd5f7ff79c3aab624725ad7e3ae7b99c7d8f2503bae81d69d4a35466beb3ebd12612a52dc52eb5d9819ceccddf6c867f84f30cc43974b9e8654a0238b5360581d1a8f594d6e67bf59aeb2172759eac63cb53ebf388fb77458db2f2c6afb471f2a69ab45d8e5d81ab7fcf22f9e533cffb1f452e1073d0fcfe0a74ee2d1cbbd52cba36d46dbbdacd4ba9a0c2e7ba6d75250bb369be96805c6c8878cae0c6b2d895b9e5b8179800e6a522db69c3b3703698a777f63f4612a6d68ed50c56616ea526da26e812335a470d7eb8cc9f4f1878f1e51b8749234d48c98bcef2744c03b614eb0dd2e86798d3c55ff4796f1a021eb73598e75090c9941ab79ab79fe5d79d7bc8bd20231542b6b7c04b8d4dfaac1588b6bcdcda767801ac24b6f39301b669daff1e22c89599cd71b89aa6fabacc8fcbd8ba934724dd4d3d7d69bd2304d199a557a2be21c76bfc4a917080bb65d6c6695b9af89817bceaacf3b60340db2a85263ed65d290b5fff996e335ef4355d9c93354b3fd5931762f78bc4f608c5b82026e96a24b113cffc32be6d9aeb99edf182aa0e3d74b2fa18aa2cf5ef9b7c10f37f868ed2f0fb4612c1649ffd7e64e7c992f119357c7909f9838b555294cf10c3b0d112b19c1909ad30de7ef4d88ac3e08758813a86950f5cb7dda935d48aed7443600d49c282f155b7c4903f2eee90c4ae1e083fb741e50e85f97a04f8bec3db05396e42ed3343af76fb89f8ed25302a466d2f97d1b3fe6c41f55ec1c8e77aa9d25e94de62cd0e6e2b304c23387e7f04273c1adb5050c7496f97621d44c782aa7b998ba0b759d96a728d004b1e823725b93fc2a3ed20ff2165c9e3cec0b8323cd4ccfed357693d5f02e0b95732817e8518f2762859ee2d189dbd3482eb8cd55cb795550cd4cec73740c3cc0b8cadef8e427eee1d1e07d24c98ec65b0fd298ee2ba87fb916954e26747b4cd4341f7d23c096b798e4c6257bfda54e1c4d12bdf092de81259ac950e4a94f632d6fb00b9c793ce9c0283cebd7299567450aef8da9f3f8fbc6303a172095c8d62aeeca742a098ff1158e126f8319e4e3268670b795defd44a4e3b15d3fbe4b213d1c372455be3deb94936574c24ab1cb52969008bde6d1e99bf4feb8a9ff5b9b958319db5a7638fd9e2ab4e4127572c4f044cc1bca05df36a656b135049d92cf631d6b6fd9fc28ed477b1a5b9caf799388e3e43f8ce2c4783dc575c4c89996b61994f4186c52db765fdd0506ac0b017e050a1d327536173f8db6efef2b75dc254736771041d1163e810385d8e102e8e48114757439fcbf52f065945af6119f99e180acfad126cf845b881b9ff161d9666abf13a8509b6c94490433e1fec3a9b417908b43eb455d9e426f5f32e1a564f8946207e082a342f2cb1b2c0dee1fb0f5fb6f9fb9db7f6ff1fcff88b7cc57a9ed05ff69229da72c4bffb6393eee4e87161fb642e3ddede450f81201eaa5daf4808aa22cf40a617bf1ee8facccc27cfc462a342168d907a82cb57a0cc40c738919b52093f022cbd90acfa87825eaca1f3d392eb6a906b08d51622358c6a8d42429bf3967b5593b4af4cb88f6a365f1907b438b6265eea410c6a5c989b84b8e02aafef18e2a90e1421439ed66eecd2eff7bbdb8c4ceb9fe8b2f3a29a590dcd0686b32629713b5953c921b0d641a9309f4165351a72677dbee5d2e43204894c9714d9290d1a2dc350d4cadb5dbbd7030ce6b409849537325e73e05958e69e7f41d515c9aa918f68824ec438747f96957b05a3c1de316a98ff1c239cbe9d6098473d1dbf35aa02864c950a6db04dc661c98713bf80fcdc92d5f2935c4f0e2434c821870e311ca7caeaecbf4c02496bc6b9c5cedb494c73780370b65a386689641699c07c5ada3c76d3adc01a8967727d5bb71aea620d5af9ba776102f68264b0e27780f6190d222f105b0df871efa3899b83ad2c4e10383d65971f39e5891bb2cef9166bc8594a81fb86195d49b8dced6ce9e849429099ed6b47a3a863ceb01d247623de07fd4d5e8332ea80285346e87bbcce7f908387943cf1a5f86a001c777b2cae9d9b41beeafc40b68cd9ae07c2cd3760f1cfa8605401b6a756da28de1fae6335b1a264fa88e02fc27742e2d77529e0e0e97a0d581155355b1502c8244964de8952eef2c7dc716fab68dd60318b80edf16c8f66a019c1d3e819f8c265f83cef0b324d6a1fb2ec6ecbc5bfb55930905f198ef47c3c5d593f12ef97ce28109ee06b9a6bccc47f400248c35d7ed71f822bd23c6e2c50e0032e2f4e7032217d1f4a50fba0fc31cdcad9ce5eccfa1b9d71ec16047ebf2694fb07b8aa2e4960efb39456a417c201ad3b9d1f3f6a70cdf6d1132333aeb92d254ac0ca7c19128fd6dabf2b4c571ed38c9c7a4dd393249fc32fc42f22857500206cfaf22c942c1c826f11125287e8be53ad523f4e84898a55faee92c352223d598f15b884d3e4fc5c287cf48093285d5ddec1f95f1fc0a8e46fe81d5d9ca8388f6e57ef6a29e9de02a1cb128439365b3539a178c1d2e8dea0346b9a727515fc6c0920ca5e48e6c4007b20148e36cf81f33e7b403e6bdd4872f7ed2ebe6d3fe7669d267810d0052aa3c71b623a142c24646e8c69f35f5178f661c2850d67738ba2750582d956acac4ab897bdad8eaaeee804a69fd6d4db7062d34e29a760bb24b502df4ffc521fbd280386af0e14804e77e2873ea2950a5ec5cee71752a52ebb2e5b0bb9b616e179395ecdff548fe1dfb458f1c2e9f84563f73b00868a8948df2c76b680d87ad2528c6248e9474a902961c268988a1eac199cefaa0b9445530a7c2f87c8458786c0f5e35d0cd4d739cd062cc842bc4fb2e374d67db938e619123f0a645889633bcf126ca9e478cc5268f84ba87b90db147220b1b37a376f1707ef0a6841e55368b368c827b63fb41787587553ab3efb3acb48a3258dea5fb332c23c586970ed0b9bd576affb808f9d65cccf6049c7c7ae88eee7bb6c509987f17d584c96c7de3bd2f211b712894232946aef3b76cc709b293f04667267a0e0de16da36480be0736c95582bd94e45c4e08f2b980f617cf5456b55b8e2bc3d61c1581a3977445ef356051abb1bcf2b0df01a02e50d0c01d06fc8f9c821c05dba6dd2dbdad85164665e2cfefe7f2023e2434aa08186cc7533c1d7e4cfc4508a6909893f5b1ad1bb0ad084891336011e59e3fcd02a642e235c3e94430fa5db92f1699ab9d0e50b8e50ca2f701776ad548ac31596e1eda45c72e66e1a9deeb8b40c1972f4b0e0cb71b9736a98357dd5a616688b9f1b60c506aaeb45758701196d2abbb979a06b6d72aaead202752bd35782ac0958bd79733dffa1d35471475e16fa7483c111df17efb3d37e0199e60c892e6e5517a0d8f9f7bbc3d6b316a316a8fe59726d6e7742aea735b035e078199176b3e440412699642db1ccaf646f7978e5ad1a0acc672dee53770515508d2e9c2f4f4d93dfce5d3c9af0ad1000ca706715671326493e36f53cc8b635126daa0d44c79244e3b6d82679b2e54bfc7305f91baa4b6e1efa694971711143b88b9f285d3676b4998936ad0c1b2d8413cf745e75194b0ceb3b1d47dde7587a5539eb9b590662a5f90596e3526dd9521956bb085f818e8b35c847b71eb65d17b8350e2eba15081d6cee768726a6f5f8122e7deb90cede7b0033dc30218f03815be38b5b78adbffba23154a0450051655ec0f489c797fbeaee8aad16dabe983f8a0e14bc01a1daa6fa2325e479bdceec57e5f91862d7864dd6461c16fe7e489688b478c717300117683a065e64185574b512a02583d3bd96a9a07624a7edec2e2a1554729a9a5770d1d325ede689dcf166414ce6577d7ffb2d63f0dbe926ef3ce96d452ea25e83301b6fb9dc2c53e0adf01d1a919018a18e6911d571837c5c3d4960cc960252ba76e2dde42335bb0580b744114ccdfc5e2dc84c842745953da60097ad7aba73fc01dcae276a2a54cbfa4c311fee92b565371438d47462a14c0537f1ff4255beac3b463482621d78f205fa8ea4391c67c828d578a53b8f49ec78b820f13544361b4a7152d2f222be06385e3ed8084fd179d9b207d759236ef27b69c665bfd98a3057a90c0907615fea8a1e5ac6fedb91fd78be4ae4e35fbb7d7fc8c363ae3a0f84a06f3698cb22c26ff6ce387d02620beca75b9f217651cbfb1ce825ba1b3b194392c9a7b9d40181e90780f88eb096262fce941dfe72b36d9c9ba4ab921140b03819a8a8ac6b4293a43f507844fd48f164f3037f9d96640b1ebe2d34ab8268d250784fd89b60e85fbae3cc67e30a76bb2bfe6450cfb7a9d52b449f4ad3570be0f1b8da81d43320ff3dc093d297a18382c738019ad5f8adeb6e8a67e0ab7cc6a3521095e9b4372340acd4ec2698a00ea5979b69e61e050bc932628ba4dc304a0814a2575a56a31593419f9b853c78bbac1f8d9dcff24edc62150ba93c5e3bdf863c1bc49283643aa97215f5ad49d7565fe59997ec23af3c38b1f1e3d2405ee37209ccbfa3afaab9806a035ce677f83ef020e5129440aaa0b3d8cc988bffab6754eb4f41aeddfcf0aa5b269f35a1e4a2a16e1e657e99d60e2218d45cebaa8ba6bc8bb6f5616ff40566d4a15004a10acba298d98a4451265b1409dbd5bb21825f19ad7473c29663ae22b388fa13e8830961e65438fe480d787167f637678dbd71a59a2fd2695778137819c279bf3a1ac9fa5db749e875b7a1362b7a506ef8d11e6155e5ecfb93f36bc7a98cea0682c8b1980009fb0fc830d724340e5cb01bd3ca675b3b03b31ffce7ea898d9c8360b01531359878452ae052d9870d81bf303665b64968619a659bdb67a68ee2883305048698a34bd465143bbabb59833065ecad6b6db7a2a7050fab0b3b84cff515a9809a92b71d4f6d222652cadf21d6c0b21de0162ef92f1c9d27deecfe70c07d9df62564b154a342fb65df9f71f785d4a81c2f3808850103c64db182e96c68c5bb6246c54550c824c174b97b0209232918127efc5bfa18b2e03724e94952b9ee7c6f253d09f73a0dbb27f3cc9f0f2a3cb06c39098ad44f3b35eb1e8046bcb180dc0f2c1d937ca33a65f7c0d9e38dc621298b0e9262068740af2a47bbe0af8024ad4f7bb721632c1526b6cbe9f9594540599f72cf73746539a909235b4f8ddac36bb5eb1c999c2246be4f7910d4dabe0b9e87e99ba23124eb7fe0c8c02da50d34a3e7f057909f5ae1b99b3a6f3675288c5db09fb287168511fc711e5e307ef35a268a88a2ee023dac3d0da86410252a362abb0ec2d747638ef59867be44ae1f7c8e847e594e9989dbe2f73cf8a797c61d7730dc6aa016c6605d6325d7fd21cc9786660699765b45394c8dc6c6cff6a0eed127cd2ed42afc8f4a4bd84f910dff2bb0aee92280943d1c9615c35cf889a1940086f7b9b61fd1c71354c71a09a86ed32423de90d34cce350c614dfeda8c600642a7ba28e4b84a5ed0865ad19c5c75716da1a0c82b963a8ae35185806477195f6fe7b2daf453c1c61c1c0d394261d82d01a0b7b75a6f1ee2503710602dd315c854d5fba695b7155518f1760f0d2730e5f9ce89f50bafc68c9a0f2a8593b40a580a5bffa00e4bc8e4edde181111a3d1f025e50c6dc32e72701055c513fdbf74c401d15d708731ed3f537633c7c1a3a8035036c9a954533602d17ade59f198ac09dc4335923a5736caa6383279a491a135a040897e72ac3d435838fc33502bbef045474ad7f3103f4b6928e4fa5c99cb9fbc017d1955f474c4e9564cc109b0f20e977a4926f92d905dce05542e610be957c021593d1a9567415db1be1f2baf2f691f9a7b6504f2ba878cecbaa9d755e11604796115f5a0c327b367b4bf5daba89ad4111964d8efcde42de0f683e4da415fac1cb7321b3f5d6224b5b831927b2e6e12ea014fe66bb893393d55e556966ce02ab3bea7c7e40372c8b612e09525e565f774a17b22cf3e018aabcfe4499f12993b4d7773192d5aa422cdd5fb1c8520219f511e0cb8c771d4f0649e5ce4027855b8bdf5a71238f0fbda651edc1e0870cc57307e26d5bc0cc2b7fc629b58982bc738158d03e8d33938d7109e8f54f0a0ba7272d951b8a272d70fb7276b6575f9680aba819d67afad1b3b9dcc3f7e90a4727fabffdb6911c44a714c1ed0d4adb86daed49b2c875a03f17ae9785c2fd1aee1e3f5d21d7049cd381d9b6f6786b9b95dd50c577dd8c47e0c39cdfc6b39510180d4f690711e4e94db259b5bf9d3248e87a84dd61df1ede07288d048c2337f071b46b0565b2694c4c88de1dd1c6b0c932adec5374078fa6d7e0ccf00bc1a15dab6f6b3e99ae49b628d19cd33d8ede9619585807654ba59ec9e86d57701ecad733bc533ce2c50591caf0d3b08ec2a945a5cad95c8d0c1c6da952acde1805606c682e1a7b4009dd40e2aa24bb90a23728fc65935e93902c75985ac0d50364e5a15d43dedfe29f6504039647d9bfc89ebe01142f7853e6c05529a2e90511359e6b3d45e166aaff34b5a2eedd8d6784ed24d316fa4a1e382ea68fb313abcdfbd79ea72a7754108731a48fd162cb51fac3a8a80a90892c4764e4a810c336c98c824107cab445a00b923a8fa155641a74c6f321a81562a3aba9c8bf5e1c882f5ed87d026a3eeef6da01cfba40bb1d7ec1e04c29881fc068c63b2bc260e1c9eea4a8fb8418abb96d16b65b8efed795b6f4db6d3c24164a7dd6a623c5e945d5c760ca3ee9b9f469b003e3ebb5a7f23175c4a91bc93ca54ff95b8b9055d0b25416cf1051421ae9768ca976c230c33ac5a343037a9e5bdfed8bc7c9c490b5d41766480b4a23f88a1e3c0af40f74199ab4a6d3545ec4c9f7804e2e6c5f535097ef78be84ba99fe4b30f7511d32d353fb766e9c71691bf71aade6382a50a485eee656d1251c907bbc3b8b8eae9a91923300d7e3a9c3842745a8d2870ba3119211b66aa2d3e6bcd1b7d592ebbf7f4052b39e140e2a28d7037d199623fbd22ff70cf54144a2338b53b937727d566d4aef6656c74723d349c63228d42f3e4b317fe7d789a1951fba809d3111019c5a532650ed89de721575b52027ae9ab17c3488f413ef5218458ded95b8a4b3fac185606ebd50b16e5a85d728f179854f4c68b2d1adb05c9ac2fc04e9d4fa21e69c9ba3b1508bca19c21e204492d7e137bf2b574ce0f136827c3e9b0c0beb08e98ac3a34450dcf9b4b6b49f236c3219e8b284c65a7c9c523f5a5f1ba89c90c123c40865fad37b1bda0086dee445b7fc2a00f660510a899cefc21c4ed07f6fc5555901c4c7eb3f3aac291b7f28d9f9fa0ac8c07da08b5e0f74ad49ace8e5727ecf7c93efe527e0c19edf58826e8836c654f77191db6bc2d292fc88d656a8f6e954ae569ba48f61358c22c5cc03406f07d7c107989b37084b190a78690b60c03dd5f2ffbaecf76ce797a70e9285cc8df4ac9bbd86f3a99ea1867d187fc881b8b2696d9487c4cd966625aa57ad0b7f514a4a2a8549aa35513f4c03e1fdd586dadcccff0ad9c06cb056079200ebd0a4b68e2481a4c57c9bd9323ee6c293be8e7ab1ed96bb2c89148827fb2902193771d96d68c57c932d2403ad6f220c93af8123176087997f1f8ea9079e11a1584ab50c305129c32a6683fc80460bf105b0c7bc8f61d7e2323f42013ce882200b92dc7435c13381cdcebe7cea406ac61a57f9aeb2ee79441f417c4aa3f68025f643ee030385eed5ce8271d7eaa6c6bd03a891a3e6ed3b415bc31ded698580a844ff58bf7e0c663bb3fa4345f9bb83a52f3b584613bffdfb05d97ede3bdb697d94d80f8904efdc6b786bf7a82c8b7434485e793a13056e73fdad9404180b5132363eabc256ee5e590e8b1798ad6402a2be9b16f3503e0fa5d40fdb0ca54ec21679b9260a54de51318416a04624e1ea2de046416876f6acfab800120e60fb3225da55ef84c625a74609720863b3a975a1b41dc002f98aeb569f374ca38fc132c2ed7b9436f615655d463411504e13714dbfe0a884ee64a5a4f919fe3543817c91aad61e3a18c51e8e17155bb7f2ea7dd5a40e151cb4ea1b4b9d23353bc16d1b99ddc8a3663f5e7c295a4325c1fcb908f5ce43c49463431d95e8e540757cbf8c71d668802641311b2ea1fd0431a8052e6e3bce448d8e566556c1e279851c74ffb7130af82980db1de797d437ce1cddfdd70893afdbc8b8fda2d655c161cfca7feaa1dad3ff9b5f094b039f0c9bf1a13d1e84fffe6bec7410acb24300b4d6a4ac14929d2982abe0a5ebe4a0c5be83a19eb2e29e1fc01cb5e494639ee4a81eab100778cff90a678139a234511327fd1c2aa80eb534b71f4508905dfd2fb980dd14f0dceb9e8bcca88c8f7ab45f7807f5e6c122de42340e920a030d27fc5e5ed03ae95738942d477a36b2188f4a47a5bf8e80f694982c9a40773eb3924b7d561afa8a467f24472cefdd2791dbd9b29c299b809dfb7096fdb5d5eb2ef4b4cf7b8c044679117d40a77d785af7af80806a42088fcb0fb386283d04b230e3ef0d42335aa790c206fb17ff8170272b1813ca395db75d5c6d84c4e1ea14f922bbe81fb71b3c9302f0b0c6dfe20c580baeb4c21882c20a98ac731ece0c4e246f29818325c6053a22f4d28b096d1604cbfcc5d1f45a8472b7a1bfcfe2cd1b1237987587f544ee13de4a2ec3f8aa0b8a1346a64c6ebc67bddfc354bafc8951429a31502e8cd54e1a7ae9ac8cd17d745dd915bf3e2679b951631b53f5a9e539092399b68eb50c70625d2d8a28cff02585278f00eda2cf70aa003a297b943724cfa313e15cc0823c05562c4aada14087488f00cd21685252775380d01f2dc09ae19a65a766e93e8e8e6ce5ff03ede3726028bd8dac6e534da59e2212e7d8f2b18aaa184b0afdab67f161afef9321df2f4db03ea40775aa9d1f7f046e9d755975a0a06c11e08c79a92dd998239ab9af4fc6a99e967b7c53ec21bf69aa045752e51ce10e1a395a43a083f4d69cb65248ba3d2f94f378923f262da1d4028ea74b5e59b2cd3b6e829903be9a814fe770e27955d9d20554e21f83ed038aba9a5a47bf07aba284d17a1b69a2b0c26226baa621df81688b49265684158e691214c9ced06279f2eaa329ac211a29beea62494d238cecbc1a5f8d5d142703946eb73be749fafe7e8ff8f4cd6284bce5c02863e129ced83d293275cbc11abe399de43e4ef8a6dcba201af2ef374f4f0737732088ec25f8b3dd2bcdcb75ef25ab05c9e40c8264e9183f5eba07b6dad0be2f8bd919d5939a0a1fd790044c81ab27153b18bc124795bfc1860ecf0dea6656bc9d813c808b5224f7b3fe38f4f8702aac68d64b3671b8d0bd1eb74fb3b732dc41143fdf4bd1805df63b9557213905b2f2fa578a6aeb8e97b5e41958ecf9e916e85549d3fdac562a0119778f00f2a15583c6b2c833a629d6a8d737c9fcd3d07b98a36a24efcf682ce5eca8be1f57791d3d0143861f52e69b4611821c053d52fccc95df7eab89ecba7bfdca357b2e0770f473c44423369adcb50709098e905f4e94d94c8f6630be9207783b6cafca3e4d6419fb3f1f2d0e5da6851f4ab45e78b74f79d9d44e9bf740e80fa29b3f2c39b642631b646358961938f4b253413845b574d007c78d895edcd5579d45834c799b376f633b25ddc1b9ca88f0dfa42b84ce06047f2520a3b1b9b434c7e96fdaaf5ad5590902f95c4e87ae4aa48ed68b09f1a5f2f7e3600704f2ce098f0c9e109b6ad5c0f502bd7f8a535b5ec04c0f13853875cdb2f180a453ed0ab7b6bbd2066d3fc1d18c128ba97cc729ef22878c89e30b1edca8a6aef802953522cad0c1b6da03cd4cf235958265ae7e7ee63d26e770168e607c3fbb2669788069f458ea8e0661c9e934fe0084e9562b05efba823445baf6d13c8c6b01184d1bba7560e028960395a109a6c4902ba8dd70e6513ea5c200be7d12469d7551c985c705cb14a5f25adb0d89123d75a72ad12f8958aea887800a0ae656978b001216af93870e3d81948aa6004bf6f8e1aa7d9988a09899f145840449a1c40edfce7aac608158805cc927c55ca8f021fd4724726b3ba1d00e012dfd6b5cab58923a868fd477694fdbd614b48ba444cb85a1df1ce509584365668fb76dca625bb735672075006c4d69ad7334640922293ed084decfea3a57dc073be55213d665e724a126a5eea4b69ff14ae5851da651ea17dbee20a014df6fbf29cafd46b6c86c81c9944baade3656cfeede13c6a1e0763545e6f3b35e62092cc50abe4358167746ae162e93edd88011272ea7449b08c48ad400a2e9746a9296aaa800fe367df4c32670bc9de4ffa51f3cabc84009935f738942a035f4959cdeeb12d5356adeb6d612e8e5c3d4b1968cb4160b09220f6b407cd617808e233ed22d924cb7124991b3f718cae83b867cad58ef219317349784bbb522da90f723e44539789dd302c42c6c47d896bd07707347966aeeacaed354d3bd42bb2e568965f1c0a8d7fa7cddc878bb5470a3d594f3381aba44a4042994ef367e3efeebadf7785b2bda89d531e7e09685659b87c5946ca3d02f72de1f7725c209bf21ff94f74947dbbc75a11ceae729dd1de5f9b5711ecc98e32d2fb876fc81a588731b3e398f7bc001730054fa2dc639d4cc61005288a0de207f459c58801fb0466555c762e3b4e180174adcb99c2b731a308a0fc43ee57df30d213ba2f18a238b2806725b502ad846a2243ca878b87e5378a563c358b658fac1a1fc2eed78105434ac56eed2f13b280afb3ef9e113914e609d29f0a69943e52889f9bf5ec19060ec8af1a7b3e21e94ae6aeedf218e474c645935c53ac535663dc1bd761ccd338c4be2fa41c8b4d2bbb4df63b91267454a4ae8ad72296c841af315628a2b3b88fdf38c7c1ef5a4a48448c374edb42c16a6ddbf0c64745fd3beef9c0c2f0678a035d7dc4b1cc2d2d64c6e5ef5be14d4cb85046589e29c9550038a9bb759c55fc71d7988ab80a7ee0f4102b07d8445926f1e04ec0e6cf939e5a77ea85edd7b667b39cebf7911034e47ef3cf2e27d9ac86fa0d961a73e9e3c15fb931b90b0cb64589d0edd9302077c9b4d59bddbf0ce29cd510bc8f4d79c69a480462122956411b9b640042413856d4e433e0829662e44db4e26e200ecf7ff286ca4f167761a65f1c1124ab6359cd489cfc334a5dd5980d2c746d30bd7659eb8ee662bb992da74c82ed34607a6f4a1c29569e2c1737f51367200ccc244e5a0babf277fa593bad53822898a933f38fb6406e3b5b6356ae648cbc3aaf2d455835dffe0017f7cd1bb40348b58e7d9a3b7cc15109071c08d1b452a8f49a79824cac924479932aa0404183dea6cf834d930ce1af909e8f91397139cb9b83aab37a6048d038d9e96369a923be1b999f8283e79de799b06fea27facc40fe07a84abd9db21b3fea30106259308daf4e464594b76cdbbfcad310beddcd94930ed7c2cee846b6296b289de4ab4e356fa806120a4ca1f6d98783a06c748c4576c74a094b381193715e6ea15fc5a7a5376b252b36fcde5b8bb0b62bdf0da4b6a0693278f3b50cff2738919667b602142d0175a2253275d2aac78f35ce3b249f694884ce111ab532a88863b841847413d2776048d8bbbdc6eb3f340067ebe2581c6291fde97f7ed107c3f60b84e1ad0dd3242e555ef7fa309c35b73f7989ac0057f56b7c6980859ab023f7b79ab229f6732e14cb66323cbb81f8bb28dcf302cd4a6acd2f188c6676717a0a09e7c11bf9de8f60f9bde196aa2b7f9a3f0d92c07ae958c3ce36d09031a42a833cbceb10c70246bbf27391dac721fdb2b4c35d32652c0eb7f57ddcaca4acc68b77a7c73cbefa6f6811127f2290b89f891fa3e0e698447b4feab786dfde66a963f8f398e4bde0e327fe37d21cab6dbab76b72d95147613812328a644940e09a0f220c6c32410c16e037a583a1f1ce9988faf82991bedc179db1ca8056c1a5fcc40a43feb85f2136dfddc5de79d4f0ec2244bf00a9cdd3658aef9d2875b9a61ac22f2055fff77a244cc8616e46a80dcdda538fb0926589856d52cd305b30656a5912ac4822721b564e4de76a9b4ff263e1514e39b03cf3211e2fcb1596c40c0a896ed24ee0704ed1b9265beb1c3accbd6f3c782d67c897cc1b4f8dab9a4f5397e562329a11ed40e303bb357cd4eb9e92d84f0b53cbff037bcb5afbd7877fbbed64d31ff595c860d1e9f064fb9a43c05d7b3226a4c31169ab4135b03be78bbc12360c2d03f26dcf1ba547abc42906ee3d36a9c353ff8e7add0c24b38aed37280711a18567c222531fa1510e6f05e3a092392ce2dc53da12a07afa18dcd3e7cbd563fab7da4a6275e5ac049f3cf1b273653b9848d8375e9effbd76cf24766ac6c31487783029eefd9857ff2afa98f60814a14a38f7a07c1a5859b72db6ed9a41c254b596c9f9e71951eb72979e5fdcfa28e221cd6d8d11fd265543216b39bf62e5636f2fd1a32d97309e1e29e1bc78233ec88619f0e0ac38a5f3b29d49801407fb4482746362e5f24a1080cc187edddeb82610dbcaa949f67e35b1252eaf65e0847ce4cd740e5072d6c7a64e6d7f3731c6f800a752c14fb82ee382d7c5a1cb30f49c44cc08abc76f815ee20b972c0327fe193b8095239d7db977254f53795efa0113d14469725fa193681801c19669c21fafcec1c19932afce79ecef393efef1126fa36b295c5b41c94759bdfb59013b9e7f864f38b71a95677a0b90f0446302854a2bb6ef6028f32715b1d2423f491bd3e80124b38313e526daa293119c15290ce09a80a3e4deec4dc45f40294bc28ae227d2c60b2e6a1563573e39744bb26255ec84062adbf3d551d7dd7a76e956629157cc6108fd04d150a1bfd9a80d606b1aca2f31c9b1f6eec4fb6e0201810daf915afea8c04d4b2444454328abab607a7d17ce697cbe6953c08017ec6b227e9d61e7fdf33fc0c58c8fb6dad180b14d870e95a44b20e78c08f80680fe006a879f443cf6945a1af2c9b9f65d29fa1fe656e5ddf28e67247cb5f138379121a7e77219949939c3f96a6197e86ebc42f8d31645b833d5cc6e08c914f9218d02725f684b5aef6734e21bedf8b5c1ba11106d99f1d3cd9d9d00064ecd5bc1b962cfa0606088cb6459a8fcc16403d5a7fe80590e71e3c2be5e2e61b9f339b44362c84e37bf4422c7ee6fc42f461c635f7288d88cdfb1db1a01c2a77fc0a0ed229a4ced1f0187fabd6fa739d802a79428e75b7952cb8e0281c1e6e1b03f17ca830dec2a67cc2ff9f541104df6f7b938a2bf933f4a809f3c4904bdb9974b4fda7a8a4d72316100674b0f3af3fd1e9d5f7bff6da001bf8bd93dc086f485f1b6fcfec0271798f06ededc7cf5ec984c599fc7b41bfcc319f06d82511bdaac105cee1567d73f33dcd15c51213a8b0be683eee1f8484dde750238e38c3356cc979916ff38d14a23cb3d977d34fd3d4400824c866dfde35376aa66f577010a35e64114a442c2cf2604cc7f5a9402700e22a23992ae1973b0dbe8f577e9e5f345f6767c931397fbe2188396183f4687b91963fc9df88606eafbc6463a7ff0c0ae6d362c214ccb626ba43fd8aa09a302945a003c4873dbdaf052f075d224ea2599dab4a67beab7fd1b862a566719dac7e39ded9e255288baace050a2757b252c32ce13b6cacb0e5a5a0e94af8cdf896ac6043ec263e59648b4a0b319da36a185becd16ded3698c17b4316ca53c0bf5c43c1c4af09bb4c986840d64be273e376cc3f8f631da2b4030c82eaf58609c76839341c40c0fa1839deae803314adee5a64115c9f7e9a1e2c85fa9df0125bdf8238c1d8afae3688518dba18366a64779823286ad58f81fa4281ea5dd8c784b7bf2217bada95e9a63ff2df42f86fb86f8d6607d01ed0d75ba8f7bde16469ad151c2f5acee2999e07739b40c7aa7a34c350629d886fe4d1eec58f525c62d8b7f2438251da2236e08ee4108b1d8eb451e71469c65ce0fa015fcea4bd8dd6194089f391944949679ae95d0a3204ef87c87ef6dcbee4f54df399f087183bfe8a797237ff6477d1f49624e274b41147a64a016a71b46b841be400d552a88fd43b6d0664c5a4e2b1f33d16d92338169c939460e644a7dd5a9c001ba5136d8a7581e520fe0cb6efc7ed8b1c22677bb8c8c57c169212dfdcb307f906aa44ecc130ad31de71cc758970fa20097af8547194dee39e2d238ac8ea2a8481dc214f3995b3985adb0b8ceba60e33819d6c58b1afcc82288ba3ca8c6b25b366995e9019f9a5928f467f73342b55474053ef8883c8d5f26e7f887f0f73e9dd91e3bd1b462a73f902eac794d6d366b2e022e384a815cb4687e670721d97a8ff82b62321c2daab11f271045f412820211c677415198275f3a0e1157bce65b7caee632ff9fa3d6c4aa770e85828736174974d199879d0685c83a034712a32c45ef2027045b53c8e28dd87bf095eac479f58824e41ec28e78acd310d9ced756457bd7bd635ef6831977506521c8c4654495a550994f1c6c58fe9248f6d91983dc33cdc155572e296a1798e85535dd131ea5d7bafa40326ae7adc7e8d7197b4ec20f0246600297887fbf347961a84bcabf26f1723b48347f3b366cc93325b64e9d1563f0ff499ace2cfd8510e7f1215a9f1bf91a4d396bb3dba7d237db6f1d3cf01faebdf221e6f7ebf37593205e5e26f4d3ccfa5934c849e87db31ac89ad7d33bba6b223cd8f990d7ff805854d6caed7a4fb214a6372fc65f60c9607da28d53fa36eccadd0802c966a059bd127f7cba9c2f1a3e718c7ff8a493d46f6634b6c7b97ec063925ff5a3793ef5f8a6d239c690712aa22225e4c2f75aa73797f28cefe88a0e7ba02ec862723bf434f34caac3a2e26ea51a726601f80ece5c7a41a626a8ab85775d75479bc05a8e657b81f4df1877e72268c7bc578fb896418a016f174037967c003673ddc5a7b9d3df013ee498353eed0a0f11ccc0f96027a7511d6a1ef30588d35a48a6b58fdd8316219a4a4c6176bf5aa069a7a0ab7e5f68a1998b08a4d99b1f4dbd69c4626c9557280eff5c219717c98197dc0bcbcfe927383bf1fdb02e15eb01a3329ad984f183dd78a33640568bd3e8ee88d752e72377fe9f5e759a8398888e9e432c046bd220460eed996250ac44a2d1ede83f60eda71dad5efb8732ae99e646bab41409a4d608e81416da4bff4f5694a99d08fd1df62da95eb9e04c66e887ea8509db80b9cb74b82a8bf83a0eee23dd884869135ca6471bfa3fe7d866fa49a23a4630fbc5f9dace85e80f15f5f398de657bd900bdae6ba806ddabe9952b67f6e0f10c1744c685377d090de55b521734ef688aae79ea57e4ab250a688039f3c31f96937ed80d5392dbe82e29f9334bc0db400542a5e991dfe827e824b4cf20922563b1b6a8a684f0496f3cf38ebf5f4451e2841b2a122f80f3bd2c86ed5850ebf36d1929c32c06d50e9d328090a1f4a1323356f6a0e458b1d5c17356baf6d0de4488460a39bb809b069003659cf226b03785b6bfa1bd41468727d9070e6782304c259dd24aad8279ee87ca5a6b82735f6d8f568dc26d8e2181e118008ab8cdded71eba026a85bd5f9b3753dc1ee87071d27a27d0354bef5dbbcfc01419519a1353f65f2d8525eeea534128f7cd49e81a6755c2c281e8acb0dca23a099df22dd7d722e8e70e26dd8f1cba6077eabf6a2456405b7fc6599743eecf6a1b8fee039b90cca7f126b2e3824517f4eea3d27548bbefe9d37a756926f37396dc4b9efde8985186e3487f75e64f86407ad76f0a474fe6ef292489febedb2505749ae38d08d0b622d4f7ffb805e46e8bcf9f5b385741feb83d9bf07657e231b031568857395beedde2a9b2f8edf61d52742e37d98901a52ef25e8ee4e4f639030969fe96fd3d36ba8a74c861b91ba527fd247d014e082d52d311e110b15c4a089ab897fb5a8521dcc78c2bc409f4c7af73c54b3fd81ca3f38e5a09f44684aa3eb40f08eff12fd92f1fdf650bdd1252d600691ef3bd384d3c4b5db228c502c38b47fe086e231e7826ad1b24c0ea3d619227fc81addccbc6dae726b694e220439e2dd2950b2a0c26a6eb0c8ef92b99fd112ddf39a1f8348b416910a3d606ccf25312018f823ffd99d9edf00e842b71f0898d3c635031de6d571ded3e35f604d634b4b1ef3c1bd27c7debcf6a31c3b26ff4282c145f71dbe77160b72ffda8adf82019d71cd108d7d7cecf9b680c78e02049b6ed6e957babed0528781334be274a1e25f3dd38cb93e86189e395b406038450f773cc03d255e0702d2154665c7720891c726d64d258779bdc176249e48bdeabf9dd2d80737c16d82052d000d769b267d884e6a85e615db0c256349becd2445a97932adb6282cee9e9c9343e6701b7ae3c2011042ae2f8205d53289d18b9dad4d7ee6b6af80d8194b91addf854640df6a9cfd0307cf6fd9bb641c96ce296712e855f427cbd513b40b8aa66dfc6e24d89e7d06968cdcbd6bcfe1d174b5353d7d2b1f8637276880c26d76f5c629f0b058c180370dbfd22758b7a1e0c38dc7c8bb1c7737a4f70a1cde062bea95465eeeb68288abad007847a524e40a061e585f3d4c093f1b0ce826448688919bdad6ccc27be73de334702130991a78e61362143da17cd37653af9bedd685e6c37a7832ee8884c0db9a70dd4a1eb997fa0ddf7741cba0bde040254b5c19fdb8b82831d67a2a4bdf3c4ba2e11c43019eb4fcb471df8728ed20e215547332c0a9dfe17a4e571d621bf20b59cea1d32e4e42c01d15a55f3053b87ff3a011732c55619a979d7bda48d9972998949c88f34e04e282daf3c9f1a4009ac3b0c4be519f4d003a7f5997a15a7436b359cf6e45aa90112560d8a35c0a1efdc97fd767bddf2d44914d66e0f315ee718f0f6c3e39ffacbe4a6083886f75acf5173828a1225ff537495c4d2636c5c86191cd2f0902fc5166f9063c9bd8acf29bd7294fa8bef6a3fc185b08d9bfef08a8b5f536321c1036ef7ce37d5f60690d8901e5a383974f71ff76fdc83e3d7e0381eb755c8812feaa68a4721f9fc49f6219a7b4303655dbf3055e86fc754cceed234b352e0e7b25b3b6cb081c74ebaf92b5fa88bb9d304d71b1633240fa19fb4ddc69ec3553d2de62930bd8539ebc844ce0ea879739c2bee2a6db99e9dcaa745b3f532dd97e458b41087433bfe9f7265fc547acbae4b1a9a79860256971a19f8c90015782d8021d8f2fed49d52523704b94317dbd7ef06965f57b6efec4604f6103150be3c5f2fd1cc0305ce867fc7d23241a36a04ed736a1ea4f9ccf1176931f50d2b65d1081bba366b4466aa7e66d7f44462186c3e213f938ca128eb7d9b22b9e354b0f3e028519bea5cf68e2189c21c05658bdf19cab9820f1790506e42d7085eb2ef42b5e04d831a81eba088fa821f51d6eb5e06a416f47007aaaabdd221474f0cfada543929dfd6e446b5b80844d49eb03efa152074e57ea7b9e17bceda56bbf86c2e9e02ba1a61b46af0717b423fdbb0234b262cecb361d32016e917756ddf2e5cf451c6ebacad760ee16711bb01efc733881f115a755dabf95aa630b47b03d9f8f5148a9145e9ce2e1af6d9a414c8e18216c653c702d4c09e5240d4e6ea65c886f8d2e65c93b4687a1cef77f921c542ff9eb7f3d0acb04d4008bc81a67208c56955f96dc50259a19b37d432bf1f868a2d17a59cc2d608f17f6a5a64eba7255adb75aec1ae1c6ff55f5fc8be89e7ae8163c5bfb2ec83dd6c39f497a7e58712c6aad0ab330731c974cb7cb0322af7ece97d51afe9829c78d3c593bbba8fbff4d4873eede693d6519a635acada2132079dd86d6558c24305d63e8ab79ec224a5096c73b2ed8a2c27a510d628851d5e6a7a3c8a798643be5c2fcdd963df2f32886066d1180e755d276f88d6719c2d7f31ceb257d68dc918de00b5691c3ea636b0da46abbdadcd2e1b257bc0d7cd739825f539d71ae26fc872d335b361bdbb87f9d80ff25437871632e478c74f2e00dd8dd71e4dbddc6ba301c9ce1670d67da5e4283b785e51a5a39895f0f746b52fd95ffb94cabcdafac6a01b5a77127ba2fbb7b44f284a120646c4a643adbba431bb13f383a33a92abd1bfdccde86888d44e45f8206a77b2ba45985080b6484044e4ecbb07c54668d0e61b507543b7926f1990273b4b023fc7f21d4b08db8aa569dc7d3896b1068ad882177aa9389f6c3173a6679982337040ebb5a032cc46495c395491f9ed61fbe36a33f0b66450a9f233c2c67b202136d5acff989af8460353d119995b862637507383710485151b859b6ef43aaf2c1e119a2a529507e7e3462653f58039beb6aa5c97f93df9b13c6d0887aa3e78f66215099d5a1056924eef03d440d9ed3a9906bae90275660902f412721aaab00bb56cd2e7e04b455806839e10cd7cfb9c45e3d669ebb1b88a624758808efba43e7b5ddabdef7a0df6f5b1f630fb924356a47d1d34d7de89ea7f2d53b68607ad23ef41fd2dd1842ec7c0dea1c48080238f9e5772585dc8970eae41403bd5edbb3de019fda9d4046df5cd94d276df6b37b4c2655072ab1b7fa2fce55942edfaf04936b74bc782f1ca519f9d9a41a4df2b25e035bf4ea89a936918af137ec06d353a6ca96bf6c523d3c43cf6ae9483e6786c62d9a8affbe33df393995352bc6be34b7f08e08985e2d44dcee612b1f5a0c658e080150e26220889f5745f7d559bfe3b16f57df5c41a5918f54677e7ce07415741ca383c01ecfd70747dda2aeaed630c8b39f3764abb7576f125e0e7fe68bce1b195d9bdbfb6a770b7389572f6ed5581b6c878d6f1b7d545d2928277d9244adef3626ccd624d9eab2d6107390187255cdf067dd73c8636cd1361693b51bfcae2e150c98e2eaafa2a1c48054aa45f4050dc34fd73c252d8792749537911a002a550270231e926b6968d481d2001a9d412e868f003cda857f6fa2b5df05f51e5bb10f7bbf78e43a7b80d971597181a6d54f2db069eb69cfc7d934f36081e8d965617fda18d2e9b5bc9941cd378ca129a74d61d6fc48a4c5724ea08946ab1096c97fb1443822133a19a00db434370121cc03c2142c14dcd17b6bab81a5bcf8950357da1fa7fbe1c9955668a5d9fb8fcc220c4acba6ccf910bfac059b9deda440d14e781551addbe60bf925a49ba281c9db204daac034f8310059c6b0e61b94c12bb79fd7a4d82028a5c389d1bbd1419073d189cde5c659d523de1f018e5e76fcf964610e32d5243a3670797397c9b3b42f9b37dd972afc1feb97d33853b05ac4d5b6a6b1537aef73beb9fea99a0f76f97bfcec7df728edb907b8e0c8c9df905d867858ff4308dafc043e8788a82d0b4d8e78968784bf3123c5d0ae7e4104cc49c7de5c9fbdaebb1b77ff05f751d75120ab980454af0173d05dd7b01a7868733280b2de0d280451dbb39b67a5abd90293c04a69eda51474259ea26349adcd5968eedecd475123a7b321f92753d098e78cc039cea28370b193be66a0d3970dd0903de2f147c9545d9e191607c5096fd5c33443e29fa7674b5062c9be854669b968d2b7723861e5d2f6eb46f8a1e2d64b9cb3f18fa3e1b36ae88790e1ca2e3e7b4881f9c442e94f4d516de4376ae9545d06bb285814edfb1ba6b493d3c4873936f35c1dae63f39a06aa01a03acd519c1fd961bb899e1560553d0f9ca15793ac0fdb1e7a99e28cb9e08989d65dc3908945d2685d3b8e8d78d6bf631cffbb498051ef53b49588c03bb3d3925f120272f859435fbb83761fefc21252814619044ffdf15a6ad5e1447bff708c59a65b9641027d14d698e2a3d3ba467c27fde365baf075df2f227b1153ec3f379fa66915812cd6542fdbbce8797bc314646ea5e4cb879da1229badcdbe29fc516abd253897b5b19eadd44de16db10f62994154cec947bcc93bffec6766f313ff275cb8dbe293f88eb0ba51dc169e4f1567a74c1f5efb81506678f18b5c4d09ff5e564e8d2f5318bebd130102473dfde2c6a6df86df0ae1e533043637086c096c26622d3efc2d7418ba2fa41520b09d42181e479439292dd93cbbe2c70be31312e653b1e4e3ba274204eb82b8e2b192740153d429dbcbbfe104b45df45d9ff5c9078f70266291792c2f13afb03fc5a9274d009d4744377906e490a8e7d80ed19a29333e70df2d59540a61f57cacc4cd4b81f6d73d3ba7e0f26ab0b764252a7d4495ef98239647f0219584dbcc2871d4f86cb9d6ca7276486b7159583183588c70867a8b7ffa736e8670e48fb1970849a0479fbbfb32187a6f25363b4d559b3f0d028057d1974a5987ea8a94e4b08c7acdff5d9dd4e597e3142ec5aecbed752f84408ca1ece146d8341dd188e0a1a4e934ae9271dc25150d30cca97a2e051b7a2ab89518d4967c4bcc0cb89720cc296abc808c40b9501b73c20336278840974deb6779b835a0efc1491a9bb2b26d696cd206a5ded419b44d94ff24a7df9ac862e7b799bda448263938b72212646cdf41f439fea196ba5756c20e8312e8419d5a6db30482934d72e8a9ceb9e23fe901c7eb0cb416e7005b248b0a80580f093d3dace8f5d707db315f35c1a694a2894f88fce082df39d07326cbd16893565320c7e846e9990b0bf99d4d38a20cf6b36a856eb7ed93ce62d0ed0eb73383b1ddad2472d83d14d6a579c205e8fcc461c93ab7c0ed691b4a5c4fe7dbbba103b7952c1505ee3fdc1f36ce70f4728ba7ca3a778785a3642295b5e7e2a5a8a54d60cf918ae45b49a2cf40b756f8ddf1e0268d2256025f58f8e929cdeb813a7047e56b40f547429f9d6201fdad0e11ea7258d06702494b50749ee7b6f0adff4ad9433ebfeff1698721cd1ecb84b5ba2c083d253506016e5361c5e5339daf5ae84cedec47332d5ba88cd7db07b71ee5ac56682bdf0e193ae24b7634de6dffc0d7e5bd5e60fa3d22172a97864b78afd52476a64230bd55efd49af84714da53b2e619b5119f089207f08c73a8867ebf521e35c763896dc40444b310b1b3deafc935cb82a73628ab076549533e0c88d39d3e157f3af06e9d02577752bb21dffbe0f304292a371ed75e63cfbe00d8be34f03f13c06ccf8f7b37b9242c321cd79a140d5387396f862e1c7404400131a62f55298e19da6539e8c4a24239477959c8e16f23438aa9d8174a891dc5bf1507e762d0ac088bd7053c4d6fa10b3bff0860af3f593e9cc158b69ecf9c82a1a21730d832ded7e5a450af85e9572b2e19954bf0dd2a26d8f9f1a0c1800beb66e35ab13a2438dd05762663991e6efcf0d04915e078c88933bc19ec08c7b50cf8e6cc9f29d5c8f999d6eb2eb4c9db6eda0b9ee943a7e4c59cf416f867e325db4222db05bce4e8c9ac0aa510fb74a61c0c744afb86ccfe84f4edbef0b4d070f92db52f5658659dd20538196c24d42d381e99653463cf2dfd4fa6ef08e03147e778aaf159cf51741c89f04fd6926c648e9a489dc623fe2ef3a91a5fcb52ca7020b56e0426c030ea8d3f9158b68b590e991d65de43171a491279ea3cbc19ffc7d05bcf49a11f6e1ea2d291ab6b6b42dbd12a1e76d5f1005688127d33f78560c24a50f6feaa982d2251f7ee731f900b44ecc2da59324be651988d47ac2342b2b54d91d1d554ad0c7f10c888518fc69a0b2d3ea9695056e9ec88800c3693944e70624d86cc7c2ac2e3bbf394f13a81077cc5cd23b9c0f58f71492478720fb1b4a5e3ecb4a1c1870f09690be16e9fb133bf55a942ee6df6aa6cf9ae7ca4ac89083c6e9f0f031996e13ad4e15246c3c2882b88a07581a9c3b245840653c9adcb075a8ad0ef64afe858cf83da591ca400a69d23661a864240f57a528bf438cbdbe27e6a4fb655c4bfb6e605247a84dde60c7bd25e838262e9b7338822fea0261fbddb88c69c2aa99a83f6f3625991d34499de9c5d6949fe70ae634737498dedaf3ee6e5a78041507e4ad1c20648cdd9fbd072a4173da11c8a64ffcdf0875d1414f3a1bf275ef6660ffa063c182ce40e85ead5f3db606055b93de4e32960c2bbbb89a46b28c4f14068dcc7df4ec642392e9ea071be0c4dc782dd90e51f3fd790705d73330857a34dbae625ac30b921c17b2d13d49ed0bbc2ab2e02a0eb670cf455331f65649e2282864557a14c3ecbd6b40cc4d54f0e7f466a17371bddf68b744414c6cd3190bddcf2419e1598c57ff834aa79484d3f6ccca623ab202d9620b06179290dd363b1da20a2145ee3d28603e133a193ef8a28d11104aea291a4bc963ea6a2b4476b33873deeefe1dc109b8f3643f4c025cd869c84be16f41fe59ebc0b14aafaa4ea88cb52b7c9a1ed76b5906330a7fa938344932d1f0b5a607597d3cae3e0b69373fa118ccad31cdd47882ef36aa494e72093dcaa9fec0c2231b755b6f22300b6aa882ca9d40987de4220e6f4ed0e87b090b34c03272675501aec386572fc6b02c64c484f1189fec61044a6142b52fd6bd0be5df7725ffe90f2cdecb779252e108a0e45e60637676db207dc07ea9235047ff6392a88cb3e2be1f08d082b1aacf4f76a6cb2142c50955212ba438530722dfd03ab7b358b37d2a76ee3bb2626d4b27119533a5a8752e2803e1c54d6a01de6b5c49a3c39170e806c7f223619e2a99d97aaccfad20d37821294ea54ad5e1e1f43c4278e41c81efb08bee02cc5843ab24ef9ef2177c597aa19fb7f0e2b498517a76cfaf18c6e956b283aa1ed08db4d84dd1b9fb8d3c3a8c604ee85b8797ad7ebd69d26454714ac51af55665de4f505c0a0e70f6d97c7375f579560ffd6b395e923735761efa1c8570f8a3e1907651f7b49548ccf0794bdba272fbccffdbd487b83a48f5fbf477f36367b78ab6757f0250478abed2ad8668375cbf9a78e455bdd2ab15c3ddad86b221062a8140f233d8e957e2a14df9782f91f48d39e64c8591245ee7902965ca7a97c5c259fc9439608f5ba67a609d462ab06d8589da10fd0ddaf96347fc3e5cb580f68f7a0618bf0f7f89c1da508cfda772c407023ea74dc17717b349e1c27ce3d47e70aa9288b07019771ef4873ee3ead44da2d978cc41bc5e7c8728d31631de885d65db1b069d4537065c5aede02c948bbb2d0855b5a56e58c75519864f795a51c96ed348ed8cbf3727f6246c4cc70cb19eab15f9736e8197728d53461e1583b1c1ebd4ef396d5e91a90154d4d3ef3de6a29273574aa0c42db051eb7ff24d27ac542b72f9f2a3fb07077a72fb8ae8fa5077db96763ab3f70a9337e2d63c0587f9505b39000dfa481fd3e421aecfdb4373f8d72d1678ee0abdabb2d37cacd7c2af98cdc0eb20ae6f8e14ed94ff28d95a321cadd34e19b2785a6c77fb2991106fab813293b0cdaceae04a237d6f0a68cba3520221a12c4f70cf28171daca31455c8f5c934310dec5968f444cc5f4ddbf211a375ad7091f5773c31c96862a70f3d8cae37560a2011bbfdd09f67549f2685cacb106988f1661843b4e55c5bbf3465ec1c45c4b7d100c7378037d37a3e6c87f80d4ae1d67d8ff157fa31fefd1baa8e064910ffca0b67f0efc19cbb4793e0e74198f38b9683e0cee4b1c9655a0be898d467d034e315ebdbffc91425029c04f49066a220d9166497b559db15b29d505f48d53e656b6d3cf6c9c6510e933750acb8fbdf976696acf59f83878e15c52f29b243f025b406f338ccbdbc6b2965eb49e9df308ab9f050593b5ac60d569c42debae5b2c8b19ef0374a59b526dde4ebccf363587d8ba5b1d0a9de6d90dbd6e0cb842a75a63feb4d7f5633887f576c2a28c4614d81f6bc2a39a303b0175832da4b28dad56c646df85cdae83348e912b6e76471edaef3112397f95bd647987aab0a412c84bd343df6dbf05ed7e6c9ac3135107a3bcd92f93b0a3e71f69e868399f8c927664e8bce7c79df4490a96675e115e26f4ba1729c8433a0ee3db948e9a580cfca7f23a41910b9a7b1443e1d28f5a7406ffa66940a55b5154364b4653595a97b881baa5d40ae135c1df30e7591955b15908f463bb7a129523888c0a910cdd207e0a40539216ab4c721bde4b5242a54bf52a825eab262ad7f0c8f88ac9f684ac22f4d71c0eb5fe40426e8faaeca04866ce2ad88b372ef3e4626f11819b4b7634f9ec49334bc3db345c64544649e6ce52a62c26b872e6629b8495ffabafe7e4a8dbd84e3f93338c2142b7215be0bf8deca8983ab21005f22a89c4210ed56fc35f9e1a3c4aad3d645be7e431fa6394e6e26751d6fe9ce181bfcac69eafe613bea5c23db387df99a67daf608ff722e8e670c6197cdfbb48e693cee48a42e350b1601eb24eb74af8f62c9867d88e1089ed5ec1c0c0bf3b26d8bfefb870f8ed93d866ef93250edc3b96a48a2b1db26c74e92e492ae975a2ffceeb33062e3435fcdf20e3d8283aaeea0bdc2c2c3b34f9490559a55f849d829c94203c8c0d7d816756eabab7acd4f928902d5ebe22d9577b81b0ae5f08c8727c64dfb6743578b77b2f88776e32bdef47a14149a18b85fbebf9af664590e69d4f8ef36279ca534ecf91e94e7eb56f98a23906742211b5642995e89e6ec67c1218ba0116788d43118bdd7127115f77f3ff723c3663ce86c67294d72cfe82a66be46aae4edf2195597978ba21e9c1fa2fa6ad30af72ae85e2b831bf866625b055bb698482afc798fa645c060c0b79b2650b2785adff3601dd4d3520163069e27fb825088964e0c5cd15966fc0f30a724d56b658ef178cf4ab6754f371fb2566e8f63c7e6d541dff49a70ac05fa6fed7331aea2c5635fb99d42f3cd61b0d53f8fda8c33d02ee14919b2a72f104ea4a80398f4eddc3daa64bd0e32471dbfc5df5b2da69dd78ed94cf9e7fa5ace0f4daebd685c30f2bdd31fdfd9ee7d7effa3f5e3805a2b5ab99093591c3a9249c4cecf4bc0b18bfbffb44abbd24b22b7368a36d2cff2cc259360af92d567b083d696e57724c024bd267aec86385cb310629b8d8a7a110ed115b66ca9ba8de54ac5b985f5fe320c213b7624a594cc003488cfa4ee9d053d0d16d9b984ba3963e7e6f91f1993f8e53758b2876eccd9e4c17564a40c2d8a7c83e9ab8cf31860324751dc3611e71a759087662387255020d1bde548359b38f1dd7e995b481c56465437d336a75b23ba45779716186c6a3856f0a77230148c47e8515781ec20fc4454116103f10de8e93e82f9bcb432c7876a14e4dd0833fd1e1da5f1bbb001ead11d28aae6c1c0cb9041dda727b7e0052dc497fb7bdea8acdfbf28b5c82c6b1f1905b8e722acf10b2d2c5fbe218c2e8f6f025da932f3ea2838a8bca551e1243345f9da7e05e6e5a5e354fea85f1a57edb0d1bf3f46addd6a6d48d6cfbabc710134268043251156642e338d33c423bddeebf8661d536a533661f656baeb145253d3c0f812ed3c1e63bd515e8a2e1c9b9b12369c1cf215a716ffa5e4ed3c77081c611e825fb9ef225a424dbfc0436b0adfddfdef0e02eb4b58fddc57d97ed9cbfe0893e2906ba4018f3b24629b814c602c31a83b1a4590cb66a82993e6d2294d067b02f4ab6a1a8e57404902652f091d2c7c4840c1b986b9555aacfb39c77348157a66ccd058b5c1a831f7923d92fa15112e82f68bf40afd4d539176ba8cb89a7408e86d4da6afdc232df0520e051b06d151419c870da28339c57cdf0d1b16e019bad29c0148d564cc8fbb6d60e837b0cc77f508f9bef3ec77c550803de29bfd404d6da0b27892d90cd7bef55cc1a60ac4ea3677991cdcbc3ef9214790c71530f597f5f017ef3ecdeccbc1f76e32c2f4ccf679d68f02af2603588406948ef23fc96c254f69a056464c8114b56b7f4c633b8abb9b4c00912d8f5784302bef2040bac8755b5c39db3acd70483409d175d0072831273c14c48ce20cc67f28102c1bc2294300cd404eb2b53be1c2c67d1767ec44ebffdd1a0dbb587e2a8a326bb8ae787250616d0bdde237db2f80cde63d7bbeafc19693aec3711b6b2913c66acf247851b39ebd0dd58cbc83afb6e57f91492aef518ccd3df7fe111819e81a1ed3f8db7ff523438ba8efa0cf168deda4f3acc944e97e2f9a5872abed333f8d6edc23e3ba290cb5377a2e6b898c6f3b0fa433e9b0a5e46c78e03da3a529d3f354987cb322a1d475abe58d5b588a665f87e29c427d3e5ab742382df0d8e250b2b795cd3b3ea49c81b785a96add2357705ad7adfcf4b257a3afd9b13e2c21116d5ab658bb229ace08b0d2834fafc49601cce23eb344839820c5b7df1a86ebd006817b2810a16e149556fcdd1ed3ec908955a74f2fbe420ffb17f3e8bf30dfbea0ea911405b791c7225a5167e1a552c012e1aa38e9b25234ec7fca876d2f2c34e1e758d61b75f7df8a47c54ff9c4dbb359972f61d89d3e7289898af6458f2c73bc32b98536b52625b21e61e2ff37c0f31a42589716f1f3d80c95a46a60aab96b119b2e621f018735d43865368a9983fee5f6b31161a9e1ade367e06aaf195ff5c630c84ed594176d2fdd735498cc9034c2d6874430440269f161733cbaef7f8a177b79c10f772025988c3bc035da19697009c3afb87bdc6c92cda80e4be6b7cc3cd98252f1edf9749c1248756f520adf2bb00f4aec8e613ab9798dc800661f13f2a6b3b8683ac64b17980a268a1ed07e317acc0efe0d89793f3e0492c641b9cad9faa016a370a855dbfb3cd807bdf3a3b7b3c8205b2441681314c1befc05511b62fcb2923fd1c8a782c1d61d66eb50eb5190fe582751f940774c9c540e690a96684d12cec4325faef668c39d8636982dd7290ba256aa1416668eeaed51ee6224ea4a15d61250fc3948d036d901478f9cfb2965dee49b6e30f061d78764e529bf7ab3c6924507c3f20934cf4a76067882ce31f8885b8f953c7855c419828e709effc147041fcb7dc81423c2d295d392fdc1a3c8457e4844c04301a3ab55cdd7e81ea4e8d4964aae1ae9333c641c9314da01df8c648d7c9e664647868facb59854e5e51f904a1d5beb181541598582f1083cc85645cc2554a104baca5d3431f465167aa86985c464bab2f5ec7369eb33f829f495d1a3d057415d2ce32875c2e81d8ae18eabb598b708fdf144796ad34f123324a51e2a4b978d39c8b8eb1b717f3e9085eed66529d86b5d4a012858784e1934e6456a211ba8ff4b810141ec388e0943be5db2afb2728a2a6358a5c4057c15ec54200d0fbd12ef35f6fd7ec39709ff57c4968babacc6cd32b723ab917a566258913b76df5281fdbb845252375f1dbee7d591717cb21d48d1933e9648eafeae193841ad0f60bd39b9a89f76e5370bd2757a5a64cf96c2e953ddf81c6d4bcd94f2bf88f65701326522c34ed852cabaabf118369bff94df313730fd2f8e1237e832e2f5d7db343f5d7890234f11615d2ee89b61a89b9afed07b560dc606f255fd1cbcce4e7237eadb0feb72346ad708173eb08196f6e33cbeb54bfd6bbe2253c91a12c04f9f02070321455d93e03f2e0993fbfc9b539c0a03b687e1faf297f54fc45d6e3806aa997ec5ac16a5d1c83137d4869dfcd27f1ff425a9affe78f88453b3aa371bf5768c2843cbed564e1a27e1d2d98a2a4cabd3a2e00992f6454665128193d593cbffd89fdd8b30c35d40cfef0ae39c00b98a02a22ccacf5a791ff6d62552ab03f14f2393998beb49c0e85896e269ad3183ae3298a208fa62d66b234fe7da5b3bde66bc1420622d6a05f652c3ac1e9c2c9e8e1850056dd36eb504c16ff4b7a8ee59ef8501b193210b148eb3d0f8827689a796052a89381309a1cd3d700dcce0b16d31855e8b6992639f322b83f8aa4f8e07fd2312606ae48d82fdb0c119bf5a7cc0873deccd7ab6cbf420556c85110171147364156024617a3892862afee40fa6f1ba87cd490c0fa5fc30e4386aa9a75cf4fa7d15c440ba8ef15e7935f1495865f0efe57c55f7d13b7bfb416ef8b6f9fa3eb82aaa2aef6bc74826b1c24dd3b62a874600fb4d919800a424c6b7361e568a9cd68ec7194bb984075c2c2a17672f4bc914721c8356c1781ff7ed0a486314cb32a92a15e20f7c706596e48995b5f015e7a24dc8a7f34a1ea4a2b0834ebde0ccea4b53d6aec92c5479ab8257c3a391cd28599c51895f7f25251b58801e5b8a326b76faebea18a94dc774b84cc55818ad53454c5422347c7c8937e160edd303a972b96a35927b3715628940f55f0bb9e2b0f32083db30849c02902f7bd9ad6f941dd9e7f00bdb6c06d89e44dc8da814bc9a31211cdd01ce6d024fec9333d72065497b1d831041492a9f7db81014ce953242b287fcd173c1ff95e72b479a10cf48bc74c81f21d8ec663f2e6bafcc29f306549365f0a7c17a860c01580fdfe48e387635956c1beab5aac7a522f6b0571a998388989197d12cd5b056a5a5cf1ac88d8adecdb2e2467f435e94180a4bf041e9ec2b23ba6c0096232059d119cb5939d40f68cb0278a7efef379d7ca38c6e9e11b81ecaef721e6f6f50d3ceb95ac4b9837f5115f027d44ee99d14555373f4c713b57d0eb4f1a4ed8ea98fd9bc927d8987770fe46573680cafd921693f28f3bbf3cc0e36e0fd12b03d12d0df710f6b7e0b0215e790039446c0d11b52227870d52ab71cb3be16324b3a96dbb722042ef74408683433c2b116a9adb55ae31ac75e0cea54cb3b49491d92f50c9fb5b5c48d8bed420a8ba868e0df79cdf80f69d848a8360ac2bbac3280488a840b7dd907ecb2c93a94971b75e011c14eb08811bcb6715f6dd6dab4c219e8f68db581812ceba29bf28ea1d3405c12b41922ab00736b920985daa819a9175299847ee07417d35ea2b340562bcbc81d2b802b1d9c2be1b224dcfb098741a3d6e8e04018b399f4e3c68a9205752b6df7a200c61d96e9521b7025d228cb38367217444a8722e3acccda09ab9bec59ad2daa6b3fc264ba1dd2864d97d8d87e1a0442e57d36f8ffcd55b260ee467915aa6190494c6cb351f8f98a7b80b76d93f867d3ed9b20fcd4d06ebbd11100c85b4d8fe5421b27c14f4d2ec9713026d4fb9346c2abd7c49966a8a649ad6f0c5580148a9711a161eb7c683731266051f663e5d8f4deb8f863395aaedac507951a18cd3cb58c1bc5fc764b6df04709522ee03410c30a31ebb425206156a41312e1f65a194d2a7c6c4586d0e0d80436e33fdc4ba56dc8f109415ab387e0f6515862cbae85d7f62365107ced3e82d0fd7edaed9da76d3630a05f838ec65e974eef1416e45c0eaa6252817aa1d4de48294515b338ebc37e317d83ecf6d152701c63b4c0027c04017066853e01eca6fe58e573a25c4e482f0bb58fc9c458ee8b769421b2bbe96db68394f209ebc91e5c5d36008aafaf0910824744fc98b6050f4d67fd1b81fa7ad7c84f566ae00f6637a6ffcbae691032eacc13e7c8d3cb34878876eba3a399240e900bcca1701d10fe744a3cc02409eb238c4d36e4601c00fae7d269a8a2976bafd239eaa5a2c7fee83d62cb511169d59aac449b75c5ebcf82c2ad7e5a843fd413ec4dfbecb48fe3aafc4bc1f37a84ef82f3d946c4d33ea2cee92d7640c237683784388ea9ab9ca5860671114b51c4ba54bb0cbb6b36207df8bf55a0628968879fd01b151fb932afe4ef0a64029b97f6f3f8f34af9c5732ca4a5d21f7524fa6188c74fc620b4af54ce88f18fe57e4081af5583bbed73ddbd442b5a960149bf1f9213993e538865c08c2ddb15d6f5ce7492ad09770328a126f4cd13c667ebe189f5deb48be261e14b2e39eb6ec457fc40bf8042dee811103e96ed232914e97a398b7f1512115ab7dc3db0bbd72be456a980ee28bebf2231383402eaba840ded519b70471c1f739e50825abc5fbdc5c0b0467275d68f610a99962442d64bca3e9e52446dcc1345d8f9d04c907556722c40d619889220d48343d135ec4d7ac4e36ac761948f3792b32f434370980e0f16de6f19fdfb2e53ed09fd78bd09160a29e9801daf44a5340ab67c67dcbbffe790d1ca858b3b6360779e3a095f332b3e18b657c1463fab2d1647b2d5a9338e9041c28c65d24c1fdbb7cf4788bf702c7f8eda0bbf38821805ed10694c3f7aa016c1f88ff83295138d80aa484f961e9e8c65ccc6f4f86859a912f00f2e8bb3bf042c1bd6a0fd796ea48c3e72b03ee38bb4234c62b84482fe7ba261f5a9c626f61ddb981f7f049bf62a598abc5b4984e00bd85a3331da2dd652a70afa983c8d54079c4c75b2df880286ffe267162d9c19872ed8c21da78baf7513d91c07c7dd69d16b2bbcf1427b5e807986fc8585fb186c77038e9a8479d164397854a4fcde0044b2ff07aa6d1a2d3fc544d72bc36ec3173e9d1372663abf58d45efbc8e7e4f4d7e40e508ce9e950b7105b693ab2b03dc1963d44534c2c7ce2c72e789079b0edc5b09c1c30719faf24ae9adb3c52362b345529fcc48829a22755ba81d61b79aef9d235aab2ef64edb6435512ba9cf240360761a4c0e0e590fae99f2a7115deeace93b7c4bfee01ae4ee6c2cfdb4c4c9c79fa17a37fe1268d191bd183e57fd9eeb36c632494c26fbee0fa9c27a7ad77bf17e74fe9657cd853511049648a0cc77edfa946d679c1c8a1261404f1d22675f14aa465504f124ecffa16e258700803d4ac04447a72bc690a788ca8ec70f4ab33b522168407231d84b984263f20d0a13483d62285bc0febf93670efc3ba1d31c12bfbe43defca20476476be5ea29f78b46a8ee0bad38709c4818bf35cdbcf464cb4b409c85bba7763b7b34ba46c3db949bd201a26602b2d757c94a80e32c0bb465cab5a7f7a65403ddc56169f4ecc3584d897e551718231bde504829a768c7573f4fc37d74ee7cf37d78ccd8d967b834d55566cab35c81b6f822cf5908ecea34814825ee0c16377d763d58c3e385c331b471fb4f614ed33ba01d92bd167ec112a669e99ec4db40ec07edc44b905388a673961decb99f2d9f10502ec585d1e8cccaaa919318ea4b740f7ee523b942c270b48ab7c27f65e17f2c6cd06f1bc8b10fca4c32e68b0002c22a44aefcbd6ba49ef8a67ec988426dee0edd6b8834ae96de87d35de429fbe99238bca64c552577eed0fe508a3ae26d3376350f7abd541b15555f7fb3a1280705d921dc26cff8748d75a9a49ac16b57ca448df41064caef9151a38054217b43700ec12a7caf52341a3614c85f16f36a9e993d26545b9c0124c7a95f34673da5e72cf97239a43f24e7374614856bb8150f33a14e7b8b326a70dde09515a667e5b162e0cbb437fdd65a602519fdf5fa6240010f4a01a9bdff5d3652cca192ec373a5c6470c5cfab8f7672761b5979f776ca9b96ed65347a49223b73875ee741a2115ed59527d0549bb16924da58d5ef5a129fa3edf3f0cbc3a784e06eadb1ffb45fddcf850944986c33463edf35a5b1d1e14499adb0cb5a1e8dbb6107692e5b141073ac823d96bfa085da8d7b9f3b02f3d5868d3b04786184579acf0ad4742f4912fbfc0d2fa97a392c0ab6e33206adeec871710d6c60695eaa56e814bd9c9d334df4268615f49ea701c44598215317e44761470f617a8b9d2b000bd61348ee039f4e6ef1a20ed12d0999a4117f0dc98bcda6f3a8b33bb3b21c002cac913a41162ab8c8ec17df1ae847184d3867f6b7d5352627c55ce1e8e4449f1ec9fb650ee71a256adf99e951fb1dff95e89f452c7c7198aa321a3ce3b18b4030aa7bcfb3737dbe54266fa7020b3343e6a6d8ac3c938b4fb962f84940051797eef8fa624f19db3798498da81d7c7f37b0e4ee5301e99a5ee8cce86ff3dd628a3917207cd704a2aa5e7fdf157c79095dec05fa1487db7c3b54f4f9d35e809d0852e4eb6e11dca90821be5e357b0bb3e876d5863880216ed3f55739382cb40b1388cee8a5870b5648ca4390ed0e46a7a6dfaed4df98441b7bb50be51b407e4b761aee83f1c154e480cbf27feb79a43c4c24d42dc18fd9ad6a76f89c7760f21dea665b154d682aca337573450ad93eae80ad7784e64974d1e93c6d9a31985879b507c0612153f94c065bb2616802e9a4760c02d4ad6bc4d4183db4efdc00805fe2d5fd992b5feb667b3758219c9542aacaa8c0ff37129b1503be47a4df287c7d50106b6aeee2ca9f3228753d6ca0597820e3c90b3934e58740706ea01da53b939c5a8e71faae9c65e67bc3601cfba26008f61febc1bfcd4ec225b5004ba4ab2df79d2bbcfe9e2e13bd03c0aaf467d31777bab18c21b5e546184c3843a0b0d8cf5bab419e67f030fbf95adcfd6ef7281c993baf98c8c4d92d9f0889e425381a37341a5c663f71ceae254cf4a7e419e2396f42b09418ff1890e874019db19a2a83c6b0d6a746eaa4373399575ca329ccba85bd64d5e41113c9a78be95b2410091a5a035535e2e958c3bcb24bed99cc9d107d3c946b165542992b8395807367693abf3cde1548a0ab61735580c3af6faacf1a1282cf4f5e13d04e3d1c5baab87d8f6ab42ba60d44cda7ae97c7045b7c05973dbb4c4bd5b0efd437c505dd45b43467ee9f408ae68ee351d38e6391fd38fc71315c9c8e833377452d97919e4a2c7e9b09ca7ce4b4589cda565e98404ff55ee7b7fc59d93c7a96860c9930ffc32421daa3f5ff54ade786ca9e2f40cad67b95324393d91bb93358319e85eac6936100d5b9ed05189728d272bbb2295809ef0742196ea29d4b35e4b8d056b973d57d43ebf210dcf32428dc43233c691d1b4e0a34810dae9968fc169d1dafdf870ac2ff2621a0583f93d4939971a78a1794573fabf3b9a597a8f884303dbed5b6bbc25798d47bd2196e327f006aedbcb67013cb8000a25135df515fbbc557e51fd37b6d4882c1e42300c9e2b0348ced4d46b604bc25e8ebc5a00feeddafe505cd656a7162e1dbad8eeee3d97b294a8de218e54dd79e1307befc414ff306b5085ab7e74d0e8fa9c76f9ae7e7718ba89e9dfc0ffa58e7123d49f52a5f7fed0a06ab19925c4860c1fe74d6588419fd3fb682043741415be1622b404f82893a3d8e648f489657ba0888dfc184e7816d23bca522deac8a0c09530bba2593163350f2ff632adeb6bfe895fd220789cda4505782020385b52c3e2bcd62cf7f10c217b4bc8fdc62a2aba5bc382c1509cdf436686b89a7c042d12ac713cdcee656fc0902220d223e363da13bc83802bffd726b35a14d1481b0cbb3749c78d5c2800cb9777b4f655d6e5c23992793fff4932dbef4c5899814f8003d8b5062b767ef530fb6868ffbec784f4dcb3439b5addaca68b391c60fd2e61713dae064071b02607a2e38272df66dd1c194a2f24d878095ca21686bf49e56feb3e075de3c18cc1b1e55b004342bb1ad160e6a21f9355a2a417bea928b576620c7a0e1b8fe0f911d3d6a4936b62af178cd3a5083c99f44c3dc7dacbbbbe73f773eece17183f3aefc4512c54747cac6987e046c34e084d9841f5a61010b37d1b98b9dcb8aa311064a5bc28ed9a4a6c1fac09ed7d0c8a45451844878d0e44bab124e907d616c1e0cefacdd9ccde93501265c4c0c6b9dda41f4cbed872915f66ebc3187fd54cd5020d62a5dd7bbddecc14c9cf7037949bf04250116f9333bd99b08913196a831e41dfc8f20439db183cf474cce0acbf427e52f39c1e0a989fc9e4653962974dd4f895e743680768cf304e527646699aa547ced74ffd82b1ebff1330e7ddcbf36a9dacc6e4c573258f61331fe9e5a3f0bd73be45f74794a3a754f7a869c936df61617d335b7fc5c5d7fba311b33b9ed5fe48132b8307d9a99d160956106318ba3aa40fc59d14a07e6af77bdfb6e8ed3c3c32e7be03464453ae9053171943f3e0fb89bdc57530d9403e66ba77761ac153ff9bb88e789a3d16c64ccf4aef99dd556fd693e396ad8e7a490f77da35d685aa7a713e362d0bcbe1d93649ed1587e2bb9f5fb1cf5494cc8c6e08bb1a927ca9921e7c3c70979b88dec22f28115f61fa3152deff677c5df9c670651cc370aafdfd4e6ffce6dc83d7c63f19c653ba3ff031d11a67d6448a48656f01e8f34ce38f5e4a6e5d36f24b015680d2f4d503e9dec629615f2bf229cb9f1f5dc6ce6f13f11ec5809376372e9377ee90c7ea52b716ae494f24a0b25693d499866cda8d0ec20857186ec3c6346b74d12f62c2e94c8f1ea1a936b255e2217c39e4433e69c9e0d20be86b8b6bb2be83f01079b9c684bb6bba5813af9a626d0b5de967c0c546065fc1b694271d37cb32e1c6d3ce181b0ea0b55ccd3d84ff63cdf7589cc7851de5c9de339281d8df186fe12a645ccf61a778efffb7214313a9cb6c84ab34e0d8e5d126ff56691490dd834872f61121b50990384f82e3c2449e18e0f9aae652693e18f15a572176c8aed940e4166582ba2b60194da25d316bf20c7897ee0e605f47d5c728e24dcf3dd9654279132b3026e6127ad865ecb7cee472cc8996ce1f3a45e433e1e975ff7e9b305672c0956871b77cfc5aeb5e7a7929028e7f4e3775451b8e78ea1b5ecb3320c96b29b7ddd39c60e0dac58e9cf2e7307162ee1c03e8221f24d9eee22215ef4d6ab533d3c888b5fd030feb72fb37009c47495336cb8b2884aedfe6a4bac357bfc6d6738199457ae57fb814940a42c4c9190deb3225b903033f0b211d46e34fb31aa23c0f73afbb549436eb12bece49ef544dcf254fe04e6c549087725139ce73d87dbcf795cf66cbe8c24bf7077b7572f83d38fcc88ddcdeb9006c7e194720622626cb6e7437034b959ff5da6d8c30d2d896eb3b6a66d2d76d537fb0889bfc33d931b6044494dc94d05289f964606d89d9761d20afeb2b882250cbe6861f7dc15034eee15c70b462ab540f7990d496d7b6389878b95b1f1817edbe476ddce5d5a8b1f5e1f405bc6deb67b09ca986b2a6489ca10e6d6bcd2ca806b074e95417f17d42e6688bdd6951bc8d1a2a636dc6322589c188522d53e756d4622f88331b241bdd667788001d00e499b5850b08bc9cc1ea2dd960dc72fd0d77a9a15c52ec7302e0c5e931a96746503b20d42bb3111442320fecc732022090ed9f40f95b8cbdf267da10fe9f56770dee0917fb1b2e550758e24c34068624e611e5385341fd746479e58d6b92819db56e98d004951c66dbf98f66d6d5722f2d1364efc8134d86b158916a514e150eb4cc9bca086d551d8d7d4365ff439b584188a801a09738712370dc4a3b64ee2618038eb982b9671b0b136a280989bfdbda008e63b4ecb1cf8ce6927a00a8cb9deca21140e9f436a88e990fc254fb2e24088b4a2a327c3f667ce2893b589a6c4a08a4f6069b5f8a2fc5500b79d4d17ed66d4848c6e3f4f8b7f00ed441f82c90d3c4d5fd2738f659308a8dd4144e5b426f15d4196b8a3e6042f25eed18c08fe1d4bdb0784b93ad3bcb6d303868fd7f59f72c10a35128a7486d24d6e0757999a73c2ebf227fcc8db84d3b765577d8c3ab9562f88204d103f9514348e36dc3780d186ecb40dacd8418e620d5b6b511a46f48865868b48b10416524a4c125bc9967daf59daef04c694160d7f1c47760d67c12f34e7f01d8f579b7d658f68c4b622b79c3440b9522baf8e12598fe440ce83b97e24d7383fb1ba50890ed1e3c67b499493c755314e61f1f6230cd028b78f1d42703c0e8d05b19876c40a354cae0bec26db211747b9ab55be81372c939233b584740f765f544d677decdf5fa14c3eeeef9218eb611ad5d72bdeba0e0876a1cae939819eb36d6321c3358d1bf42efea71f0a775b4d7397da0d0890571517db6c8755f5f65cee59fe138df944a266f5ccd94ea48ecbaf5d3f7d5016145f7b7ab8d7e639529559ccfc86acbba347be1e77050975f04f76646019fa1471bee8bdd9ca6dea347bf5c68a48450ee01b2cbd40185f4dc5aeb6d4231fa7247bd755e7758f6ffe9764e4a719d1e65bdaeb0974457f299ae43a2678f7f65fcb530f88293a101f2124c9b5d5f283bae64220e86a8aaca4f153477faab56fd4d9807fd7c94328ea110bedfae31d3969cbedf8b1ae8016ad479bea294304959dd96badf9a227befc92bedb1be6635998755dcec312576b195dd5de31109e6b9f155f9bf2bb32c4acca03920788b1bfbefe34c0e4f234ceea219a82e24136f6cfe192d42d919755af6bd348d19a71676afbe20cbce35895a4d8ceaa13cd37dd38e36d2298731523db04389eea23288509559449be777f1ac659e29519b8abfea1e5ed3e6b5ed104683e518b138e78267027efd88bf6884c89fcbfff55f0d8e072f7a92ed77de09b107218ce03633204ecf999d80bb55631a6148209b89ba3210c35ca91e1a190a208b85d93b4e8e56320a46ceb697f05f847374cd2b82c2fef0d36de995feb61dc9704c719fc131797a15be87fe315ed81cf894944d508750d290688baa2c2ccf7caf91c2c3e4e521c71d99f69a97687c1d36b3facc1e024ad34432b6408afe09c7804983d37416e4a51b52f0bd8ea3fd2bf92deab268a31466e576097d35ba96b4c5fca23c1501fe3d54482b110ef516302b3ebd7390badd80d70f6698917ed71dc907726c422e546613ac5618361532e382b249870ca64349e873471caa0aadfc7b4203e97e2238edac16b56299e7efcfca28f216f8a57763ada289e3763a40c99e3179a1d8c745b1f12104540165dcbfb071fcada655bca4e62e047589bfe7e6a3855bc79c7bbff24d6f1d44c3b4d6ae825751a05100a37d01c82581b4e51bdccf9ab463972453876d522ebc404f5698cad5048bb6d8dbe4a647504a2d0e33808bb28b4f8d47cad48792d6b59204494b3445d116e45a475462dc1b10b61ed678a70efc1f8f7419b1fd4322a0727089cc683fc838c2b13779d829f4665a09df8ebe13e82504f53dbbeb5e5a2509740d79087e8a6df445688a166e6de70b186efb02879207ac4f08ed7c2eb9b912eeafcddfff4539eb20f7e590c79dd96bb9e0320b8a4814f3b6b01ed68753c8f936f7d55fe07027c29b95a85c6c4f7785fdcd34f995aeea3265a35c8c35aa6904203dc6588c39eb6e0b62bed74753eab0cb36a81c9224f88d8f5c75908f3708fc7a507f4cda29ae663156397dc96f87dd4253e730b3a12503874985f863af80063b18d0a947778c53ab43a56c4bd41494d2c5bd1592d7b235be524dcd58b3536af65b20a45b1d99c42bb3425e34b576614f16520987d1f2441f356191c4e12421d82e0b86341ba400c62a628ca36a399a594a3e0ebbc682cebaccbe93bf42d01213c918b29512d4a8d27b24e27e4a9b6935060e48c2780bd47389ae44ec8c62c1ec8f5eab93f293f27d47ec78c48b628429d3d41a405ed349bc701c32b5cbfc5dc4b300b3ab3f7e5e80af2495331b01f8d77c80aa053ef2fc364782a104b927981132b9f6ee87bb486c30bd8fedd9cdbf57db25e2f1cd72b66d531e99b3ec72b07b15c2f8d08f982077927692c0bb27d9f2adf735037cd488c671270f3bc9c121c7ba3661c8c558f0945d2e1eddfd71baa8e4300cc0fe9d6f91bdcb8a5c1f142a08f7f308835d170c292ac03317f669071efe9f03825c1d7990aaa7ab09071b9c29660ec9bf03ee4fd49fbd709b990ad55990b94c8a12d82587591adc5f3a7d4b742bee8f38156063267d919a9f2a9f6c094e9b7d099a918b7e9bb44d67fea62ef5c66de68356933147ea9e0fb42cb72b6bd5192b85a8edf059c576a806bcd4d79beba6f8b55ca9e6900784560a0535c92cac42d1eb4b9395ebd9b7655489896e57a8197011e332881f1fd64e83f87e6981dc2efbe210d08139f51d2187258060388973398b1308b93d4b2349bc29c43da1e166c59319a54adb975cef86911b616010a20db1726884a28a33594336b3033d1294d97f520bd126da83797227bff10030e56c85ccec13dfca83d0cffc359feb5b927c686519feadc104e4787faeaf69127cb435d7e5ed3240d30a04efc097ab9100607a5053bfe95f3c67507acb0b09611364e9ac2f6477505e4c8f481682180f5b4b2b157a3018f5af3fb1b5a28965f46d26591805f69cc98528720f2458bc91b3a4738d222044ba3bbd1bbf6fe66e2d8e52012111d6ef53f1574437d3852ff944053f8ea143d26e1a989082464b6077b3e669239f474de224cc1cb47c29e1963df1ff6e8fbd7ace9bfca0d6f4321db5e156381e4dc4bd2d99253267eb3b5b7db57989e7b827a97c78032eb36bd3f9dfcb31fbde8496fed01a7fda367200b1606b1a90d08e1267e4a1fee836d6de4f926c806eb3ff1e1c8d631032973a2b2355962124a5b9286a79af2fab526d2037ad1b3f643d6ffe3216782a35b2af005871c9e1f70288e2e059fb4f5c122868504131afb2327a79a6aab99880127a54e3241e9a7a56ada913af5a282869d60cf2ffacd3d290f81137a09d2a8293a0f270ddadbf241278b9bfa5028f3bfc069ad0f3659a2ec3710db94125915643d88bc446d177f2ec0cd7887621611e60feeca3a6416ba6fa1c3647e335477b2747e1a63a6b4bc945743b94242797ae5ca88932d10b7c6da6f010a601a9501d8944f79c2742e8abc0e7bbdd578b39c61c82118929ce3e0a8dd76825ae06b00acb4fb53635c799c39d8b73ce39c8146fee595199aa3217b892ba8d898eb83788ce0452bf0f8edab0909c7c592e4dd5b3515629b0ebfbabb6a15bac63345d6e82db857f2a4e63a06c11b357c5e414f32adc741436d2cb718f211f39f8b34202a02c1cb4307eaaefc1067fd81be63b526c14c11c4c9196e14e51093127cadf8fce0d2111c00e3dbe0f8d71e21ad87915447e0e1af9d841f47ef623c29a2f5f3bc26ed3bbdf1719903ac195dc135f98c36e2f5d7eafdc87eb45570adc69d9ed07918dcfc009d928fa157eb8489337fa1ee2fb246afd76358b6684407fc069a5094b8079a746cb1389fceb37a650dd66623144fcc756773b37084f3d911c70a4d270194c4d286e688c55e7fb5547450d0e34319636e8227a242b3779c94e57bfef731d1f4609463b333b55ca2a75695276bb0c7d36866aa29eb1f51c042423e00d74c749118eafbfac9ea9a01baf4ff37bae4b3cc737d25b90030ecf9e333706dfbcdbb81ab31919f6928d43bdd03bb55d44753027727713bdd29d8755347425ca3d5ec37dd26783fcbd3b24bfbf9ad298c678d81b3ab4728c67ba65be6314684ceac317284aad255293d9f02b1a15d1aa8a3c4753f4ef032c0d5bd7e9a005750976c0745802f93cf7afd7c7d89545b8a45c85614047747fa7fafb8093821c81977b045cf70e1edf173895e8685b4bc3fb97c1c6b5b304bc8c3ad8a09f5d65e7094bd8b5f61d4e799a5d38f7fc9b14efa073b4beec34115297b6a231131548da23f841302d3538b00f77b9b096b40878ca3c90ecdc865e0f7f300ab7e7f023294c7ca0b197d677d817635a01a9e50ecfb11258338642d13cc51958ddb55ee601e294fc99e23a2e50cf9e3c1e5c46c451a394a913c50ae0401a23985c5d656d7e4a109b3c2883c0b38bc1684d340baa7e42382e99695dec4978e0daecfe7be05f75f02712dadf69eae2e80b99655617c9b9f0e5d4b7d694c6cbf0500cedaf92e13eefa6a274146ef4492ade4e332db883c7d2547f2b7ff51788da4ae156b25f7161e6610a2d07cfc3569b5aca5d7caa52d106937fb95010ef4305bcef50e5b1fccb7b2d29e4c8bdacad94e308d3fe0fa3f630ce73cd4a4dc9116d7bf09f597e612f01f79d5f9c0bc6e499788c0c9f988980cd63b0d5cf7f0abac3ef574e862959a4372cdac13d7f20684c958231ff27f02809eb5efc7695e3d24e8b7526e45173d712879a96b77d3417018ab4add6d9997c84294ed68ca33f3126cd5ea8dde288a3bf291047130122cce55a480e9c5aca36f032f685db0ab191887da44634ab0d76d832f42fd5d48047e55a9cf6061818b1b51aff82e49e99b9cd2b926036e00df519300c50d941e21cb618260d8736655e8535dc297badf322858c7fc1e1dbf1ede1d733bb6f609e27cbd5d4335a61775a71b5ec8ac0fecdb671209154acef19111535206ac1d7466cab2bfae2d60da09f390b24333f20aa81eaabce6baf2eafd1d8c7637a4ae7058015852ed8c6fab5d5a107363814e55fca4703361d8e75c1293447c3d9949689268e6d79f3b09a9f9197f2febab02618c3cbbff4377e6d5954ac3faa8ea307e80fc1f3d5a8247fda4023aef3f75c869df371edda92a2158bdd61f930a2e6a3a5201400fc95d3015302c379fb568cecee8c274653ff91f6a90d4b7954e3140d79c5d5355ff2b9c6a7d2ec78be95c2c3bdc7d38894a0c411aa47bce97a6a34adae335d08144adaa6f283942ea9b9258bb27f9b026b9d80a387a7f043440d07b24bcffbfcd4e6219454931beb454a93c9003fe15ae17d2343d80bc6bb917f4871f8b0e2bba4399778ccabd61963b7def5679e806cbfd6b4280ed5e18853f6fcbf512129ef596881dfd769fac78bc50bbcac2ea752b76c542074929d8006dc8c43ebc4998a6b6989c7d4edc610586998c669fbba5bf9dc0fde460446a2dd6f19826aee51cc0829ed7aacddfac51b14d03564c83efb9ed3e5467bcb5a3f1f363a31115c842c8acba8ef470d68c53b08a13cf2dd3a0d70107ff94c5c6ffae7f033bc5c08c5cc2d06e72c8572ca6386ecf768623c2dbf1ff7c734363d8b9c19cdc2f4cb4591405828e6b43b84b33ac4b17041072482d1d989a28734250341bc3d3d61c11f3664117b582f310ea09aeeef220de85f3a03711aa9a9a0c6008f3721197b447094ad3bf71cfeea2f1ff6d57e2ecefcccd0bbf7221ac7ef4cc8d2933d90b52d0716b66123c67e6db616a266ca3787d32989cc31c9a8c23cad7d9a0fc6d59d5a2a7e254de9bc31032f97f7e09287d4770e2ad86a588baee25ae7e416f9188b3c7bdcf15b357d5de1b665e0d3433e691ffddf9f5ae950ace084882ab43599848e99ae80881909d9e40c4d761799443bb8293c8835e96a1bae158cdd845cd1e16465a1d165f0889a39ddfb46909a655fd8673c30242afa7c77b9e3dd774266763a98cc4936d78274590bf5d7405c714f10f8f1574b481cba996387a399865118662a87029d5129268652e25fb4a29b241948166ac416cc7a25375ff9912ee07c987916d44a41512e763b6ef139d306e42d309cbd78a59fbb0d55f45876905c453ec0b80cf94bf705e9efb5aedf82785d902e7ea546a2d27f224158539d769bc815bcc52544c392643de7a9c2888322aca88239092a2f3db45dbc8d7ac609c96d3fd4c47504ce61d22bbfc0441ce546c4e9e696db44410879e8dfc2c9bb8c50bbbc0b2d833a6f66509bcc891d5c5b995818454bad237eafca8090fdcec81256d3e3dabc6d4cd447570ed3a125fd352d63c0b60bd55421ac8cf45d4d782e32b7b0d5eebe9bb721d48f07f851b6630ed708c065cf07d9f7c196c4a41a4d8ff4dbdb01412dcba7e753be56666f33db5e6afa15646113ad97d0ea05be23715a96aab661af57563658ea4a3b75525429472b5ffa600d5eba8b8dc41484e9208ec170f36211e233d4379691bb9d2c660ce21ca58aa84422edb59ec8b48b5138b0ef14d61a2939c0cf3f335cf8f1474f4f2ae79294ccffd472cfb7947dcb0d3524b61bf89d9e430baed7e4c682d668b4e70d66716111c3c35b59359fc7eb2cd9ef8a0789991a01bc92a433e576fef3f72a1d7253c9494c074dcb4a9bd53b316344884f60197fd06e033cc36d0526138dec5851f9f26194e2dcae54dcf079113527735f77da1fb6bc3789f8e34324ff6ca67c631efb213f83198f12b8fb2c11920faf023902e1095af8a4e9cd9db2e3090d0c52c532a7b411f54e86a6a18b3df33f7bdec0ecf9859fb9829130e73b72a56fee7ddb448f67fa4e74cdf600735271b5b4c893ce3c41eceaf63dcb5e2cf39ef384fb69475ca5f69824fb3388d21d18985a795c06c6d8fbc8c9b7c981ee226f3e1fdde152cb65a10ac342d939f1c30657e0610efaa420ce9b21367dc2ef8addedff674716909a26060e2469528773728cb504d12bf4712bd276ee619d686187161d58defe01b9c5315a6dd44a2bd0df452d45213cb8dddf026e4ac27c233e368316852cdfa12eec12b7b3f46b35a4707d9f0ebde7d335542b622ad23eeeab1f07c7f5220d9a8afaf179f4075b2cd833f23949a8d945c21cf3988a68be2562cf6529d7e96168a6d82b48c1f2efbd8434907c13024390a986945f11e81d690ba7d58a89e3fbcb867d41c55225591a79df9afd6b1a39b40aa72df61cd48d7525740863430d9e10e38d4df02acea5ae58b86bd29924900badc87ba3e6eb61b6fe63d14da6871907f5800ce409aa3b63032a4b27df7684e13209157e4ab1699dbae55b583df52869fd66786aea0ab82bae4e1d7b2fac853f3432514902bf429b1981786eeb1a11571b46866dec800a429a4f622d5284cc29780356eca37aa83b2b4f0e06eca75c7ec020474d6f23282d2bad42bb400a6940692bec371a97a2016bee46c3b36f414cc1f31f42db94e02d0fc606d76aaabf2eae2e32f256db553af59a2ddeaa2363b038eb7407a465e2b6d879b5e2caf4023c1ad73c42e1f7528734d9da9abc768964b19f2279b46345da8d8a274d04c161c3d1d5b334eaa7323ad3a6d78362713bb542b7f113c1c2238fe7f26ecfc8e24e2f8922112cc9c9890dfcdaf131929f7a2b01e1645c294b91922256e541dc97699ebc9a885576bf67e13bd0d5f6c01be91682153d378793b6943b16d6b9612fd860501a1d8e75cf37d14f52a4f18dd82d47a23366bd20dac714fcd9b0746e1d68a25a67c24a904e0becbd5186e34838ef72f9f264a305e6c764d61a94915d6d8f087acfe543b3e6cb010c8125cccbd596101e3d90c498262db4cdf5c7922cc0a064d9c277b10eb9858ad0c67d238979b7e6d8651656cdeb3623053c124ac95f57c6ac028752d094f0fc14aa1cf92b03facdb93fd36dab1f271776e66ad1426d01b5de2d4f92d61ea37354e67211b9e7c1097fbef835d27fa533d8c6cbcaa09ada44e71c228804a746bee3ecd24d1603b0b03100fceeef087b38c5c66600c8861867e45a367d11d2f0acd5eab7b74c3fea1fb87e45501103ef77da62a1c1f7eb8f1fba2d7a6039eb4a6f44b54fb903a7c560f7c3b3bfcda05c96e7d471b8a2ab5fdc451c0520cd33ac90afd773a36823558a5e0bdc54143d6535f0a44afc463740ffe19315aac1f3d1edc12aaf5c94b0c557a3f20077bd5b6f3a1b09673febdeb6eeb1f77b93eeb18abdb315b708c52fc006218b52625adde3d4e39e82f4f71b1b8491f0c179873f7b4d4470860ac21bb5c74cc5a08cfb655395b8cba9079951133e6f696af39d1182d19ac9dd6850b8a99a3be7060713ac6b22fa72018268aa66ec555df6f8b2045d5bce13c388472b9285330c5670d33c66c520143326e4badd0366c14d2ef872873cee33728dc628b693dadf9e5bf6c5890968bff420184523a8d158a25e7948f10679a183fa1dcf7ca41f9dec977f4d1c12cc575dbb17e43eafdd71dd0b69cc44d04f557681d6376ecf9b4a0ba8698c14dc14a04cdab2fc4ac886531d67a54ea49e2d0966c7874fc006ca6e8f7b1a462de31df0e8559d673bafe316c7c402f39b955dd613be42c302f9cc8c5bb0273b0d77710ea854fdfb2a605a7499dd888625dcad1f29418ef652c0d22727f78dab09b08528a4e9f279b9a6fc3c927d2bd4abaa9af3c4050ea100834cade51e08375d8e8d5bbd0001c71c0d3cfa4b9b38b952261d5d19e91275110bb7e45972842ee5e62818d9fe0a274890f1ee87a73a279197d41992d0c0f53dd3e0c0891fe8e3cb8cbd0dcb9be90067a4091e62c9b8325547709a537fcd9a29d5920d46f10998f79b893ae178c0b4daa5dee50d421b15af9460a6d09e6d141a285590a363d6c810ac8ac6757b44461440db46697935bff586bba9e6f081791688b1a46403b21b11a9f253d6410406e9596d8da9193ebc99ba45307e1f3c33414ae8d27aac1bbbdbc72ad4e64d84f2cf8b7a2d581adc45945ec755b3f23c68eae1fdac66e5e44c0f3ad219c79bf055b396d7ac82096f89364b472b6f28c7d1d755a87c3660ac37a43d5cc23d6824c68ca0f5ac1a36b2b22888f86d16d12ed2d0ddf2397ba2c6db79bc8de141ccb738b51ad93780f9cc6ef78ce9ea00d7695d16dc85702877baa92ff2cf70e6f7cba129ec2fddd80fe5ac5e22f5d6c4e45d4662a970334b867b6b8acb0c009ac8e86759398a0a7878c00224b742379b029f87aa07aa795834448e7004a53cadc367912df5c7f00452c40c29d7bd6f3f0bf3439084f9cce07282afbad9993dd62d80056ee2a2200b2b9bae525a9713471e1cfc52c592e17841fd04d404faa5b8ae48e2c31d83032a78a63d79415ad3e4525ade96bcf4b0ef9e1b5cbe52305c70b91b15ae37fb2949e15834b9ccbb5f9177b0abdd4ff87a0c888f70c197235c6b1752fd92379d9b294e032ce31dc97ab3bd76c52ac9701bd0693ed9024017c4147efc506fef942af88bd16ee3321f61c40eb823a9a4092e82ee4749db50d93c49bf1c43f278fb773552716a25a23a95348760601418d3d78c4ecfdaaa9d7c30286731a634bafbb24a1a49a48039caa2fa417d7d4a09b87172f325020a02f6a9b691801304ec28b2e54aef0a2682eee786ef06ac4daf0cc22d02d9428fce4e42eb0ad4440bcc32f3729b650d4f9019a908a6d093ebfb0859c0159a3fb202f7a53faf5eb8e3bbfee303f36586e2f16aecb23c1f43f642e90f44529b0858f211c5db8df74ff30909e524188fdd22205d804ff047365f0a8f2c11b522034930c9d5b82517c9525227738ec489f4b9005aa12e10b3252b6588a3d6e433a2c057059f84798f23eea690c5d6d8baa78a14c59df2590a70210804f0eb030b76e3f174762285d25f75e06476994b6bf80755b2c9e95e55e23b6e8b2be064232974c3c8c253be8a634a4d416df1b788657b78533dde8d3595de2e4fb072e2741c219659a436cdff8e8ce134b9512d690a774eacca964ed70935bc9b1fa35639c69eac81a345bf4d54c92118c9ee89ab9112bf1539cdbfba74c734998df856e31cb71d6219aa8039f4192736cfdbbdce99c6bb32e4ce07088c8827fa7aab2c13ac2eedcc1f85b298067b034c939cf596b40f4a44998511e707228600551e91767d5fe3342aa5eade72ee7bd5a4f4f5bf7f2004a300af04dbaceed2fa86dbaad3d451eafbc1069e1d194ce187ba4bac7c2caf2bedc7acc7f15752f02984e7b58e498220c49539690b02c019611d81ee4624f09ec23307ca5f3f84ed3a8280b572b4a6eedf061942cc304b5d9f3d9ef21922009a4c670c7e5ec69226734cb5b41c6b32efe34d7c8004957861a35a48a61bceebb90817784197b72e7f5635b1773cb8a013de75035310b7a237a88d7d73c97d965346e2c2062a63b6fe4dc8d4eb0a3a9337ffe9038b5d7ae4301a9ee49fea206c95140d3d19070e9543bff556608a5c0514e27d66d8b8e89ff86008047061c6a52c290b9b4f70b159227d05f4ac4a08b4d3f03b0754959f1eb3528bc12eb2ae10bb3d7e0fc15cf2276c88ffe22d4e11b59afb5752d38f2e49ce5626f62b99faeb1da8abd6fd3ff64d54417f6888b68557cba8fa6b0562ecceee936424521ac56160dd2b4b3eb1114b8cdf623119efac837455a40e029e507e10f29b8ac264b457c5dfa4cbc3130f84484c3bc7c6a0b47c50e915821a55569b0f4c10a598fc1391251d78afc79771048ab26242bb5700d0bca048b5d46e7200a68187cea0e85677280072ad645845f3c6fa8a9592fdf5746da60aa5b24b179ad074726f003d1ea5da57ec040dccd6e539c6d3004b37c96b34eff2db344efc63e1c3c654c0dbfde100832a8939f2ceead6a89c46d21579ba1a4e8ff2e9eb2d4397a886bcc7ee3186c00a6aab5b4e5c2fb40d782f4248d5ce1f1c42ca065f76781a83b489f31f80a17708d23f6567de5051bb1f22a2d95f0d98ef2691ca51a22aba410a4efa7c53febd5b129336a1519e43363a1e8fa6c9568775a5d655a52e046f8bf66294c76c4e0b270785306bf529b2572636ec324e698030438be656613ddc3acc0418014122a0634de1e829ff4a8f4780f8ef9991ceb8c4bc94d60593aaec2d8eccd134c036a7b660512eedcd9501ea232cdbcbdc2e4d6794fc4cd690e71e9f67b78fed957f7ebd607144db54cda9722b0fecd759b343a8279750d1ca45bb1c0fc8b76370c45f87a8259bb53e653d898df6bcac9e48e48bafc5ea955710e2e4b8e4ca5e29b092d70f8ed4b01c41f9117c1efa9bf956e9ed868d116329854a372852875af6323f22462a7fba51623979c639de1731f5fe2c1ce0d47312e427011434d82af61d118343dea60797aa0b0e49a9c4e91131e16e2072d68eef540e2894483363dae95159763b56e53a337460b529e3ed796352057f23cea9ecd86cc02dd71218eec05db0795d9fb65148d4ccfef1425458343abc1c9ee34645cf2fc2a58a5dc7390b36b87944be9e89eb863242cc2cf06cf81f8ec69f5a6443c96cbaac4726144ac318b5461dc449288e04fefec67443b7c69ee28e0afd733d57c5a6d6f371375babfca885fbb0f04afc084384b78ab2615ad09d7b064b9c1c5e6744567cec8489a5e5b0c52a375d42a987feb262a95312527262a3df0ca599fad596371a88b0a237427ab8d2b9e64da37ec2d5cb6d4541a608a1fde6997ca7e76e0926744a11848294de9b0623dace51308b9658becacc476e437d96a73a88d101b8ff522036e568d9b98cdea2b1f3af72325eb2700b9dc44c2298a0a53183702738c3ebf3dcc51fcac3ed2d7bb8ff2daa73658418a9509b12ed594ebe8066c28c34160a807c527d2f7368c1b45d3c6ff025990017b3c0cd822ca788f68214dd103cfb12462df55eb6618fd7dc05d5bf8d6e85e0c9f9fc65012862bdfa42ba853b9e5e2b6bc2ab9fd46fd8e2d8abd40c2d0bea8979338cd7046a5dc0af6da12e891fa06560113b8422de33e633bde369d5bbe16fc78516707844794485ccea0fea370fa068a0ba8153eb52d8b58db3fefaae60d3d9a7ff37d551e0aebf5eb86d594f806018920452ae759967ec51ceecf64bb5b85809b92e30c23776b5a9cfc7c7eb5e87dc684293a00a23da8a8412d1de814ea44a0b6f2faada7b6764210e5b0f9c8a15cf571a705e1bd3dbd22cf7fb4aebd3352a7e2428e89742313750347c01eac40edc5c5d57603c4f04a331812150ed63c6265ddaacb95cf384701636c5cba2eb4a994b95b3f131cf100a59f141980f1ef62dbb2d9cdeeeabc8e419b9ba80d26220d593b056f0f9c36151b87b40255fb2a41a44046c388fee5cc15beb51f91deeebde40fa8d13d7c60a739aa5c3d0fc221e2c0211667af3fecad23cdd92817a12b39dc1bcb7b80bb6be71c8cd034abf8de3e78ff34b787038df4e920e43509dd3a21daa3d1aadba127b5f4db4abfe7d53a854faf3fcfba84676af429a8b044be2cd4b50a41ceafbebf36ba4f78eadf32a0f604614ec52d32bbf170e99a39ed6e34d7f7945a36ef53e7c2c4dd3120b19c6ebdbbe79b8513e302ba743aeb9ad392d8a51e9fcf1fef2ad4f0efc596c3fad19312fa2f7950fbe45eea7c50b06a67a49b08f093219c8e4b65de57dd962e4edbffdfe999237d756f2ec56093e3c4c1617da00eaabf77e2df97b62412232ba8433ce4814d288da36a6a70a8ab429f62fa3926dd2b6cba1d390ad41484eb71e8a8e5124f8fcd5ca78d8241ce2e2bb0d21c6a5ea67f46359560c9daef57ffe2ab8ae9acfeb4f26382794e7fe079723b6cb73f4320aa86207706db01389073ec4dd971d8aa6816c51d1e132a2bc6c88469f9aa0c8ca3fe2867d1fbf94767835ae765ec53c5da46303ae55080b7992b851e9e054dbb29b21a11df82a20dacd57ec47385b200dd5a5c58a47c191164590a674a486bd7e615732f86e70593e4d3ef0220a04c6bde31c93117aee8e71ee2438a99808ae4d8b085340d04a3ed5dde9423879ea40bb7277bd8bb7fb6089dff740f152c74022d691eb8ff51da13e27c313a4425dc04f57bcf9451433805191ed428cfdd7d5faea05b4a7985cd36e0f34aa79cafe1f47e24ebda5c1993f34359ee5199c8663b577b64c40f69d7db6678aa60c226048e130a777cff0b2cc8e64ca6cc8a8f94079ddd4df5da4c51914f862fee5caa48290a79b7cfa0a5ac959347e543222729467e518d9abf6df041b01fd307084fea20e4e405f20935614eba35c732a684fc056e5c6c957be9b445f0b8c65c735746e78a1209e4b8b52e23cda0c605dedb33d676fa75892b96c311a8bca0dded3727ef2b050953064ff8e84efd957b85e9fad1a29adda1c3734452ed38ef3e81f345d70f47000c91d5ab90ea0a062e8f5d5b5f150832057aee570524da30a9ed048d32fcfea60a9db8eb250e85445b5caeec7d017f0b2c894dda6a14cb4943cb33c0e4cb785f843281f978b9378e8e3913f22b7f77d9a2a315ce480bab70ccd7219cf77e3523de9b8eeb8cb8cc824208c958a718e892bbc366e4090a636275efafc3e817fe8e11045ce15d20ad26d62b673d658d22fbd1ac244b478f78e10c0469ddda066979c8321c8aaab374e41272ac578091012fde92ea5d7adb807e4e4a03baef7f87b912ded42940186d59cc96a58fd50b7dd38134bc47fcca15c5c58d35a1d634ba7792729258915e2523a0bef776d64b09475d64cd603254f4c72ed91bfb045e0aca2c5498b3f7e109a8e58fc3afb78ffe7bcaa0986e61529d60eaa88bdf88d07eef6fee278a5b0d1d5b11932c7a4f1009fb39396c02fa8bb99c042151e04cc05fc9ce286fccade1bcd21ac5f833f28a6117bec965dbcec5a3b0e343b6acf0b6deb2765aa797add8af6a1249f76dbb2b23a41697d4e090ca6e80bec78fb08f52ed206c954c22d22e5cffaeeb5f5906c3af43a3500352189f627b58dcb6217848f1e31df139eb4013a6726b53cc8405612d051c5a543cf9b31dbd4a31a4811ef03ac14c33291f23f9d6843d407476198d96d37aaa35d27a4e65b1d1a3718f03bd0b4760fd5d39f6973c39ca12fb572ff1a896d4696044e9d11c4776b75bb1b4d09da93dfdb0f338c4adca2a64755d76699877f3a8645138ed191709ed2245ae57396d679aafe9fc8d76030a7a14efe12960dade6ec3264b5850542a860502faf98996ba4e7c6dfa2fca07e8fb2e144715728ccc28d8689337d1b38b9e2e73b9a48fbc59eb86d0b7c9f59922ca633bcc6ba4ef34149074943ee2cc6afc2c2ad9fa80e5610611a3917411c01fcc329fc4a1900c91cde8dbdf42b638d5310ec20ae0c0b457ee398b1a740ffa62d5594afe70d153f1a89b0a7aa3f1f4e54ae9cd9580483dd0d3c972f8ebef84d080b05633a8fb9687e3fc2e2044f044569835361c5f6b2606dc701ee7ee46a7df82682e6e26e314846e4d9679a4c52c0262acefee97d417b2f11aef0c92a9f1582944a1b3722ae7220dad656fa525d8ce2b81e9ed77a8fa710f4839331f17ee31a6f3365e3fa8b29b9db0dbe82ef4d9f71685e1501107b32599e1f91118323de5f20dfe99ab32c99735774bbaeb1abb0c9417453ff0e8e96424759c2ea615de3f1074a5e032fc5a732be77638f01f90a35fbeeabd981f42321555f107dad93a7a3f91c23cf565b2bf7ffbe4409daa921815a25b2102e94f014790d1766d1eb5a36eded65c4b70a024dcaa7c86440f1b6c1baae0ee45677c917c1353351fd1d7568fa6264e1cf9088820da1c2d62ea5aa2367f94069f6b7b65aa042ddc191d02a28ca0a60177b87f434fb7e17626f077d4f099ddf5097f6f06603206be6549552bdfa9308b477671c7269029be0cab51a0a668d4ddc2921f123b1b387cb724fcb5848d488e0cadb891b7e7fcd1dccd775873ba341be51f39b372eb9d937a8d2326437ea2b6c6faf81f5a0a3cc593e4f2228f692dbc83d0f71f516e71ce615612af7b6e2ed1e89fc6e8aefad6f4018f6df50fc8d17a8eee8879145a7e90c7e74c6e5e35653e326c8b78853d6455ff7bd761619f30a4d249554b0b1be00d7e6253c472614077108a824f87c74ae82c94decfaf1a0d5daddf51e8b42a821a4a46a1f92f20b3cd1f0482ed58d75d217d33bd5b7cf9d44b4b72d331f348c3a7efe5ba6213ff8d7846b9cb20271291427a9a661deff749d43ba231e6e9f793078086272da978c48a618687791491a9e7ea1015a250df10f24a30ff621fc308b71e915f512869f42f2811d0b3b824311c5229aba0c70d2737add3d7d694d138c3c3de58f00e9fac4f8c9b7f2a3d29d116483a5248f5e84ce9c569f29055a15918563abce5d25bba83d2a01a4936b1662755488c77a7c02f169bb50dd7444c5716ffe1d1e5854eb2abdb46c184b59c505a1574623ae8616d5cd2044b01411057cdae4abfc7e5a26c4e2e4bee7704ed55c3eabc4f9c726bfc580cbe0edbc272ae8ada21d5f973d2b99e7f583239a5b2014c67fdeadcfc8732098e62ba9fc571d1e7875e1b07d3315bd14c920889e58f53b99b7bc6630e45a94923ca3ce58890bc6451b295fc6223d4071d9b42c9187f83ca21faa4b2837362aacd61e149846e241d2396eed6f8e836542eceb1ac573440347490bad757593ebd1e1f61f6e5bf5eac163baf2f508431d6926f5e64932a361f58b2dda3cc16b56476663b8c6ae3d072a907f9e520e7260a54f613c347409083b85d9802bb5f0db5675ac3c9135a8d21e450d1f57b6588af7a19f7cb03a925664e5901c14c389032854127caca4a223e5fbed17f83b861fcae8e7a20300db34af6798dee4b7ae3fa097403822a29c973bd6d9d96bc0da85c90ff5a1ccd9247d3c5a1f67e181bdffb11c0653866e32dc1a1ca5d1639927be8f4a2b54ad7cde2800f2879d42e89f45277928cff889b374a535ef617d5684205f4fd8d12d59684b9e792af690bc6e9251ad77002b68ffc6affea0bd67cf9962dfce021a6eda0b9c40ffa85277f54a43afffad4c6f2e97e0d443999f281cb439b1e5c976fa34b2b1bd2b9389df89025222cf5da5ffc2bf9e4c4c550396e911355c0de621454aa07061e3c0dcdd645621db25a59c18ab9b32eea9240f1fc930db97c0394c14df8855c3d7bc412f7c5400ea7ad66f8640de506a5960fd5a4b9b496c3441681072140cab50a82040078b891c13d05372de626ee9822d5d0a4880338b84546ebf8357194932078c652dd65f187583737db14193c35ae2cf9210b83f256d13971c85704a9a2faec40132feda22374b5c128c0d87eda8a8aeb14b05974f526c7c0d2d31c15d6c408dcf6696078a6d840204588237d461cb3410e660b42bf90f2406954bb0b6be50afa6239ea332148f86b0df6d1679bb6dc9da10537c8645758bd8a5e0001dae0b4a30c41166910f3c58a3fa3c5073964f75e82b6a10b7363e71b64259b5bd552a447b1e4e88a1134bf295b662a143ae88b5f6649e7df29e0c8f682e42fc2cbd2af0d8ee4550c5820b677be72dbf403d038a19ad4bd8be8ad8e83b8779c2049f57b7487822a2b972361e42a0080fcbb22c99f6c31c8e4b23b92c466b91c5c027b6921c1d9221b695e9b0d78b0877e4865b2bbb8d2217f0719c13d766d70265be1e757cd8605a741f8610e6080a8a542095e1652b464512c16f974de7b255c794033f41180617aa99d0a2f23974db0d44a9e3579e62649a275647f13fbae3314e9ec27c964436482fd8a8cb6fb8bbb6ead2b6f46157ed25ed5f734e7feeda8d5e2f3354d9c921a08d2058023da127b476ff38836824735c271e10816f1ebc7620ac65555e6e4bddb0bfa637639eca3fa84ef01e4d8d3d259e692ff6423d6a1e96309f59ed4136dd5346e4f67843984a6186c9c4215b09eab9bcf15ac967adfbe8e2cb9e276e138444378a8abac3eb1b78cd53e9972b47574ae4019d78319305eff3c292b11dad9981fbd7cda51854b11f25a71e84ddc9f519ca685c9dfc452479b560ecf259beb2920ecda6d63bda03145fc2e0045081c2894435ccddaa08bf00ad837668970ed825e3ad047ac5458c395fa30ffa8638afdd0c0dc41c56f034c4015058bcb00bb5ea4741f5b42eee564ae32afb4940ac74dc4aea28ea145d4facb555bc0b7c4a7448d7dc34c84a50d0c5ebadd967cf429c7ec5dff99778802a0690748a79a82754ca0b14fabe3cb92534d65b4dbdca1fcce43b717996d1fb0305f0dfb05d8891f722f8afae5eb4658a0ca8f7248e0e3b3cd12379f88c42f24eba8bed47a6f52e171975ed385fbbb4135ab379b68d3f512d28f8f559010ab8c2159fe3535bba82dc2b9749973670d702ace6249e9b7fb92a025765895400a4d315a552cdc0f273f84ae2a5bc93b824e7d659675fafea611925dd3ae62e41e5df82c4e2530534264a44dc0176fcee89067841f06f68cf9d4fcfeb7971eb6c88821a936aa090c97ed9d27a2162092bcb8d8e6d3bd6ada65d1752a1479476bb1cb0b67fa764398f6c88e42edfeccdbcbba51388cbaff997a2af8c0449390692c0bedb28b64df6fe04b5497453a34318ed7f14e732734303bc3743bd5bdef37563518e5105176e76eec8ca7eba3cc2676cf3260085a2fbcc2b0382ae440e53efd682aa52d103cb3930aaf1a16d771eed2940caa1db81a1235cf249598ce269d8e274e74c39c0a12d9c046a1900c0ef8ed4b75861e5f4bb335664a9aeda0b18a7be8f7dac69ed8ee8da76f9348edc85b3bd79c5a18cbf6f1121cf3439da7147bc0af6de6174081d01ee29c62c44b6232974c5549a9d369b07cce1d0c89ad5f3027a7fd45c5acaa840b9643beacbb80a36d8af58c5b8fcbd14d57b6ecbdf61bec48deddf424a397f565a3efda1e18d166507a6b2bd8566434448d896293f953f33f733266140fff05ba5d528c9cbd89a0db6c42d04821972cab326ee3edcf683f7f2e8b060478b2ee46e34663303e409dfd3975b512c376746392718914c65c96119e272b026c326428add61ead43eee5817d638fcb74c0f37bb0ef34289e2bdb4c8de445f0bdd96542953fd0593966f74f7587de5b4ad79cfd9d68c98d36a49337a5dce815b98f29df894004204bbbfe44d37491c4b7449126d7239845cab3fdb69bf2054fc054cf7f2e5978c94c5a786dc33c9e446997cb1eaa6d66ed6e40a9e44abe87d06c5298174d568b241f35ee59f6c5026ac60ed5ed5213d09a0e998d7eaf18eb819bbf4bec843123897b89444ec49fa49366b30c79f6f0ac767ae810df65371fe88ffaae03088ca8b9c6789e326485a8dca7a4e0300c7d4135fffdb751dd09311b6df70d8475316c49cff99ca06766551694e28663ca1bd3d7e3d4971ecaca38a09e6ee298cd91ba5997049d467072b5370d10b5d7ec35d898711d770be61c35fef487ac0ae0fff2ce11c86886fc44e78defed9d63e8631ffae842f0f70730aa76a243797ae21e56cc07366ad9a44244bdac8cb3ede253143d47fda065df0c90c45a282ff8d50329d4e041706f87e0f55604f68fd2736048677c0a42a68f2e31a9eceeec1055396fbf4c89d97c3cf3f979f4426572fa36f7da77ed33121350659b18a73d50db80263aeac5ef5029b407299daa42c507010f0a01426a61b5ddc21029dfa0b5a04d33a34f27e74a533e973175eb9ed863726c21a11b9fffdcd73c73d25a4c5ab9c70617ee611df17b0ecd5c07f7fd735a80f6b69302c85b0669d4c8cb84e26e92a48c4d92f077770ea181fc5473e813c2d70baa0bb11ac15ebf2136a10f807dfe7dd1b720442e93e8ba02447a5c23524e727ab9b24e50b7b750bf725eb21290bd385340a6065e969f78aa84847cdda88d6e17377f884c83342ec64e23d4614bf8d31b85ef7e211120dc52ff6dc01693775de52d8a9bebc39cfc633302c35b1494feb0da39bddd823aafc84999f84e64a95a624377396aade7686add95521395a26200601b799fe710f8a45f7a9b337015fd8efe547f102a51a9bfb4891339b58f649100fca20627b47c05f17bc49ee7b68a69dd07d5309828473052f176b14372fb72629436ac9dd987f18c2e7779621bba8f02d2eb4f855c0730d3326f16e6ec15c6be161e99150f34c80962cf58a83f48af32937d0e4f744ef734f24d50ba47299e74dd1c37e1461d85c3f097795902d5adc47242e04a23a2b14b60b0e445cd7ac8eafb34583331d15726bec85cd7bea7dc3c6dd75608197a003d65ecd0d48843d0449e53367c97f1078e05e15888d34dde4ac2fd11b45740d42e0015533d0dbf1dfbf9b3ef548179c09a5938b55fbf05d9bce17dea8e0aac7d973dc4d5516baf89d49bc3c1f88b7533c4efb26b2eb60933f39e5dd5c8fc6ec492342469217d30c1ab406cd0ee944cd7c3fc98591b6d56314733c8a1f085edfc0135e8dc4535e8f7f37cfb0c5446b4b5a79e1c1041d44e89f5d9489afdcb9a82b2ca96f64bde5ac62a0007a459b42f14ecd969abc646535418a0a61eefac56f1df54b7aea11aa298f30c282e765f16ece470f313ad968850aab9434a421ff8296077811e9839767e28dbf2a3860f8803a6589ef7680a8ea7e0bdce814919a8bc5f7260c50a23f3bdd4e7bca31ac902435e9253d69c0163ffec1a44b08939cd4357ef2f0792fadb2f17f7ef6a9ca7942399be88110524994754e3346e4f80a980a1ab08f2999fe9a4e4628c4a7cb2f88e157889d8cf91ff5c52b9347f82393ff5de7706e42f86a7f62d8221cff699bb612f0ca29762868e2b590526da028e1f7316f4d4b1ca4c13faed2d018845b53982c54918cd833877040a2436db0c4e5922d11a324c98277df7eaf067741981cca504e9ecdf4e34481ed7004bc046787cd17943211bf0d36339e1b533907aab7a8aa1ed8c73e889a91c0b68b20b226eac6b9b7edac83f8e392522cd4e9699719c8a0e2317226787ed702ed7f8fbb223949c5078125af487aa87ce06a1e45925889a8d4da9bbc7f0400bccfe2d09cdc1e0b45af125410f51f291ee2ee3238f6691b7b2b05a6df7deae2f6c8845b1f244d8479ff58dd369c9f6c3c8d215323a26ac81d56863e838036c1ec567ab6677163716c8c0836c79ab0565421bd433ddf592385b0c8992ec4042e3c2f7256edd536ff1b76f09f294c83f12d8f5e2b053338eb37bd6fbc49473f966130b414429004d3f1c3c4e1d042cb2d80d58882343bfed6ee9ed0a31194e826bbefacf53f354ec68f2621f9558f8a52593f475a9c0ab00ada0eb77a580231e2e0f37b47c2ea5eb252c768f21ff085f4ac58af57112bc342db5f7ddec8b450ad1cc79803c33562e6a63a69c8bf4b0abb24519795fb7cb6fb54ce6527aecd658056cd68473259a6487442240220c54d6da9ba7d2b1a7c2330fc0d7bdabfd4650634b2edbdb7ff7fea01b17bdbd697b2cdcd4097499693804f81cf6c809722307c5060d6cdc9c95212f0a56a4a1ba03bbb2d2fbec51976ad4cc3b13fc55fe93fc1769cd897f35af5ed7ba89d24daa5a21611ac60ce7833b3ac2397397fa77b2540f0ec61a9b321be383d352a6fc4730b3be97166df2eebe56d439e41ceec09b77ca722c7573cc98266081037410000a06c3943bef6ea25f58eaf76c0f632745405c7cfa58b6648ca724d6c7aa75a8683a13fa5f51e3310563315877a3c491347e93804f935b7d6f3bf3b0606f68f66f12d942d796b21764fe9de948983e64c20357d636a7d0b126beb585d1cc098542e345c47774266545fab2b57963d354d915b07bdca068589aaf0754faa848ccf22c7db15be97c92551c005a305fc736764e63fceafc7a913e129940d85c39b13dc81b600dc4ef4178a91998b5af70725d23a5eedf06914f9153da48e5398585e02e3e29f333d2aaa570eec5a427ca080e4be45e8b26787a0545643a3ff9cee338b61940dced1e2df0327c7ebcc3aadf72401a2666981b67a5d51da99c73b563ebe8022dcdd6430d7137484c13115cba870302a41c83df2b73e5512c7eb36d952366af3d0291429812fa8969c545320528530f07f94bef53379e6131f58796c8f6ef51fb392dc2757106b1b58ccddcc24d2d1af1ea3e952d79212bfe23051b847ab052df411c1c7a3ffceb728b3373deb7aaa1dce26b5635e8a7013dd71f584e21b5ba49f1d02a6465f474ca4732eecbf5a9d81313a13fca517dca5914298ff91bcc0e60a135b2c369fb5e045f5b8290b94cd7216301106add93d052b0800e41424927a54bade2c055070d8fa73d093bd67b22efb330a6ee246de3c5c4c5326bb42ff81db8137676d3ea246320f1aede3b1493490d01c1d1d35c9b65297387350aaadf4607f3b69e7e362b26230db35584710f7c765d2b78389e82294d136f0129cf34bde084021478f4918b2aa6c04dbc0f8bf87b41039221e9077f62e18cc4ff3820afb8bfb978c614db9772d4993730e0df22bc55dfd3d044b8d10ec466bcbca943af5c13cefbb5ab3e00e6946ff8feacb1f0b13f45c5dc6a2069b011f91fec132107089cdb06b28827c894d6c79a315a2fd781f23b07df23a6edb4982153144aee15d95e6de446854262fbb62089c4a0e0a5fb25e92f9cca55dfef92578cce21b2e780fe7d9ff1c1433fcc4f82afb328428a0308e80557b9d336eee922bd061b4de666213d4bd99dfde48f27340cd9bbea0c37b44a8fde02b0a5b3f1805acbdce4dd8124415e1934c5a186423909341b52fd9c52eb623b6282a33cddb30b51bc4f027825857b3f225a49834ea8de0faa2a90fd951858f7a3cd53682f81fbbfad2ff939c113b99b4a09687a8f1ba9c054a84671f5511071e5fe9834b68e0223aea5f54f58727675a33565b7fa2d644a4b0d7fcc6b1f7d34a27a28d2ff42b6c4572a1f59209023db783d81ca96f0f241ab4e6f3ac96e2c0d4a231ff42e6829e68b3bc49e2e66a51331d5801a8869f604e5774c75115a644b04eb1bc487b92395ada61fbf0829e1ac019d573a4047c76da22a0a44d7c123a78f8a0c7dd3b2f4a3299e76e4f413cd61916be574a6adba4f0df2ea0fa4ebf8d5f0e05f411c4e0e724e166c288be067c7f0ecb676c980612a24c1aa33556ce2a465b1eafe89fb52e6bba41e7a0b91c2e8e99120f8065ada754128a019c2c1cb7ca95618a7763dcbd4a0721f0915a6d0a0e8ff8c731ef9b4db2184a1f40ec04f34fd466347574babcc906f04d3f65e9857dd4aaa041fb3feba4cdc7ed26fe820070d67b10f875301a77374de5eca8f3c4c338c2e4069d246bc101387c0fca9b68bb06211a8d3e8e8d507429e0e12819791b81d830e27a6df0d26804a62edbd99d013fa32b1a9678e2bba3df9fc06ad9a3ad54cb186abbee8ce07af08fb4d006770b721f5b777d0d089f128af11afdd13be50ce126d35e80a0f5f3e9ace2972e0de591ebcd3053c4094292314407dd568972364594b5176cbb102d03a4a3bdf3d98bde31ea1723b84bdd9c614f307b8765baba286e4f448d0a1362ca358b4cfd16442316efb463c0187c15f8dc401c44267bdb173faa7bdcc5b410334aa6b69cd9de82e60d4cef61938a8fc4b5beee437666e9e3e8667c49539372c2d23a38c700185a8c4d893d80c863b2c83d3ba220b5b82cab54199c7d503477e9442c5da1ad369ede81a9fe616be441c2765570ed82ae95cb4b8887085ab036aebc0e15e5e6fc592ff4652ea47fdb2eddd24524e151b0fdaa21b8b6bcc93ca933c8ed0a6fdec888febc088dcfa88ddf1bc7577e0f80dd5bfad1c862c030119daed97453550f0bcc385cdfbcf9ccc725f177856557eb290f0b788e2df14c6c8c08f9f3580eab53dd19c4104b524a5eae592e0fb312d42572a56bd3705a173bfd7a5560146d8cd93fb52e7ae9358dfed6f9c87ff50137f43fca7337b902116f2b9dd436bdbcc97cb6e3be7c8d2b6349a3e5063547011c6676d36b1ba981bfdab1b5304fd6a93e02b2d9754dfeaa32b031208789b40e5f381c6834e0af77133d5c816bad5e8eb8d890d2ff3c31523d5205a155161b0baf8a79deecde66c65f92087cd68a588644fe0b613919cb5698982a25af41b6d75bb7b840b897c496e44f41c9987c3c1df1ef5fb835a14195463105093461aed6808807edf728801d930b8075af8cbdf9aad9a219bc2caf2ed22428b33224059e76586787edc8a49ce6906736dddea6c8e2974e1a715144387e52afe12e1468e2b73e55a8f97439863486ffeae47c83b05591c61c81d8e3c77e86aa374a14bb55f87874175871de4cc97d53953caeaebe329b43839c75393589d61aaa6ffd66f36d0dd98a357c2846f6d503ee79b85a28d0dca88a696a0aef3e8deae2876aa77499f80f1db5938e2a8983bcd74a8e5f38edac48b1f3da3bb0887f872fa237f36eca75d291dc470e0f04b2d0d925e9086f8278ed5bbedc6df8b483a48b8027871bd92bbf72877e405084edfaa314a2d08426b2f122b111ee4ccddcd0dc280c7768bab8f3a10564f869e96c4012f2bda92d6c38d49869d45427b7cc966734f4566abd84698b14cb605791ea5dc2be73e46ce8839a897ce677db692d087505bdb1f2e93fd6ac23555ca1103b365c6f6f2b8a5f07fc25875d42cf0892e733575202e2a4a918f58a4e62112a3163f602632e1ab6f127009ae820bdeb0a554326d763e5440a8ddd11e89dd453b7d925083a6b236a2de75d63801156a59d73246e58d0a3a0ec64bda5099ebc08d345cac51acaeabad4f399d60a57d551724fb48dfbc1e0d2a36ba37f5cd09059fd79872268d0e8645f05434be3cfa1ba68cb11b6e1e94e4bb979b2949685e6c688b3b029f7112edc3d669db0dc50eed7d9dcc046780bb26802fd9043ede31603178a9ff798410acd374c15def5358eb7e163eb1308261e7a02699673b142864dd37d8bff6a8ed80cc91346af547d78b857b97175bb3d725514c38aeb6e3519903c185e5f83839abf4db0caa5d604179afeca3fffa993591c0acfe10e8d5ef5e8d84b34a0e6e8652e472d0228dff4d1b32c0c1ff18683b227f4c3d3637d2c0ff8e51113261e0aa295ae1d94f39a1acd8c40803413cc5b1c1327429d66dbbff542dff55ef6468c42262bd40bc9ea99f3f20cc8ed35b842bc14781ab1c464e4cabc078379d3beae5ce1baece23f84709b7cfdbce894a01c7a714629ff4e6731cc54857133b44c9e03fbf9698e9fd583c2bc4216091151ef4d0278e5c60dbcb13f5b55692a1a70ae8b01030e708810e64a55a0ade80d52a05e9befb8cf264026cc5eb61820b81033d49e0a21743dbd88202753a5eb559f8430d7876bd348480951afce8d3f718f5441accb2c61ecca008da68f14a783d47c39197dcda183a54a687b3a5431dc7a4451ba3b771ecf4dcd3b10ba85e0d6321ae8f39ff808647f92aeacfd1afb1c11e030cae561c26b81b2542b3eca7b4230447670efe48aa0a2ba3b4fc2ac8350434e082da7ff1f173fa82a7188fd98f56c80d24fedca82847d74830777efeaf43eba8701979833b618f81e197091667b1fd52605fe54873877277335a54ce7dc5532c0eb2bb657d7af81bf9ce433e939f95cc92b72663cab26afd8b6d0bc7fb30efb7532c5a70073698fd29613fa99a0a3cc6383905aee29d17448b3d94fd8a20c8dc82578959796bdcad05336c9a41b2d75931ba95ad9d315b203021989f069a3379126c301cd00ff4586cb22b1ded6003a67c6a76f0f740da492084d0788f4920c2d3d216abb6f672314362b627fc19fb93d9a4cce76ba9ddf524ab04238384ba21310c28eef87f8eeddf3aa30f0ed1be3be356aff95b8152a88da7d256112b25b95b4106c07566e27a484837986d2c35130873ec029da043cae8474ba3f1a5ddfad6fbdedd869e817717692577603229be3919e57cc833c7e01c3f2bf5d5bb0dd41c3424355bc7776f05e8a3c9496655b3cdadd564a536c150a65f149c8a0f5681bdd55ee030036c1af093bc1335f61f76fe652b7ac2c75665edb7824e8f2eed76987121598f542ff1834e21756979df66fccba565746294a441ce0507c216301d9f59d926625e529d74f7ad544a985b33e12cc30759af7ff867fd7b4e6820fbe3bb682d8c6e5a2dcff321966df9c23523f04fc3a0f07de34515f035330c17f27bed58529c436a70d987a52325f84358b4c73735ed002f39a9145d646a6d72923f2d7bc1de73785131503384203ae4739593ee721cff96e35e5428e4ba249fe3c0b392c29908371c9e62d61e1cb910d659a8f52c40b502c0c7949d565f4f960351c2fc5dd8420c173e62cd70cb4c98c8a3b2e28b32aa4b1ac75fe63e16b8e0b949498e91d9df4e8e38d75ed476b2de7c5d99b4ab9a415b7eb891b245954bb38eb0422ae0f06c877fc6d86a8ab4b08585e57d3836e3a613dd9a42189dd4d4b2835b2c4cd27217de19a40f62a5dfe93a06149dfe596634f748da09fcaa3ced5d0fde8501c3dd5c6fd255507b4547a129b4de653902553487de5b893e636ed32521e934294b3248e4a3fa51abb92ae640d1f08816f6e8d84d2b7a5a6b81da1b07850b39848276c75e6085226151e7e2a1ec8b8cb9b6aaf050d56d93a5abaa4b20af6ba20bf698c6d0c9e12a7ecbfaed1d30e1ed1872aae4bef2d587288ec9f557fdd8b44de3adb4ab3106c068989d40de0925c176bf2a9b2cf85b653d4d39ba75c94ece6c3c9ecc9f7c595a62c9bc4abd189dce46851b86a0b8b59c833720d0efb86dab8dff49723b9642aa9b89e3608fcf4dfe6349dd8300fc72ab8530275ac9f77ec76e3ba92725dcac781aec3414e225acc891dbca07905940213d79ea6c33b546203c93e323cfeb0f1f214c9baf93188146ac1628a7135e5d762d1f843ee8c2017dfa486bfb2e83bab9d12924d9404e89f0d4834682b700b3a9b061b706adabff846d621832076f2cb6fdebcacd6b6f365fe7941ad0805c9f6cfd2020fbacbb5cb035d816453e225d90003adfa96dbbeeeef106afcc0bc82b93baa1aa5c36e0649ed8a69c88464858a1ff8ebd62123fa2ece0e8af0a701b78dabe6eba898ec96ca2893d09cc950e68b4a5334423f1adbc4b6820869ef2d619554e4bccc0544f475aa9a9f8bcaa38c93c8517b8e4106223f71062d3760feac5b74b578ebcd947937d2e601ad1b4baa4b70f6f154f3c5d48481c1052e096576521e4d94ed5de53a2c4b3568c236c663950a93389c69d6b811005162a946ef9371c158b5db7c2e32a100152f8530e00935fc90816952e4752b4f229f37debbc64743e9e4969a7d21cd1fe181ed49fc87336316df262c9c010134566bf2309ebc3f2180f5ff1085c747e9e36941cd3469bbc02fb7ce1ed452d46abfd7586edfc53b79467e61d0838ddfdcac203ab06d419cdf23af6d0863ce04560153b7603c921831b5678a917310e2604b08fa7188c00f3824865b26d86a0f6a07aa125b82c2ff4a949ad34850a0b92ed3873a82fb7bb8f476a832d481ef131361e00bb11388de7a418140854ee118b1b6ef7dba9e383de930ca4cdaf002e061aea705ed153f3a0d53e7dd99eeedc431149c2b203cb338ba0047ba10c0386f8bdc2e98959194a75b76dbcb3918dd072b5e4b1c11525b5362fb9361dc536d7ad503d3fcdccb1ed457e22a2d552d03d60cdaaa15a37704ffc2dfb6ba4bb4f0d7efcd41a8df03a80ebdc402c2dd2477b180c108cd537739a96938461011bc0227cf8773c08734173f548877e5cc0d67ba25e92d5e8ee1233398911f4009ca47466baa0b2c9de359b7c5de626503f2a6b03ee3f88725683e505173fa3e14b3107d267116bbf3d346d32e328729fd01905d1c14a6b50482468756f23846d21c8b8cf4596e7deac6958f329262bdef7a7d757f87451f909f4cfba77edb913689e8dfb55f867992802fbda38c1fd2a38f0a456f9974bd9b268cbfe03f4bec7d197f1aee2dc625ce514808c334ebcebf26168c30f4814e83aec6cfdf192eadeb878559a966aec333748dd08eef25d15aff01c5963437fa3fb60824b1a3cc5e12b3f6a7402807bdaa8a8b0d0f1873c4f79919ec194c86ef548ebe31bc8dbf3b18ce930c3c9d7410e210bf1b98a4c0f84cc185867e5537f3d7f220a53cf1b3fb34b82ab45122a3380876a73fd7cf43af8ddd3f0da52720d0d9a29b47042f8b4f7a95f4d049d2993b01b5c1e5ae9d5c93341814fa4afb352337278bc00cebc96fe0b3ec1020f3b7a19b813604146faedc1887bdcaed1b0a18d1e13422b380b137db3789fd8913fe6c0af7c857bb814e630a27d76f9acbfc24cf217b34a259043f6716b5c44712dc387f8f6ac76dcc90fbda613c8dc54c0eb83485b9ddfa6c5a5110a7c2399a6bc7f48ba618601f22e893027d8baa92bc136d4eba9be6721493a71b510fa94459369a72d0bad10248a9f1b5ae50ec1c73d0bceabc23857b746e05651dd64f358a8e94d4249689bff32a688da8812dfdbf6844d024d2beeed0439d1dcd50c6e642deca5936a6e12d90f2348d88e5e067801ab35dd147470df076ee74a5a05ac76184a1eed99f737df1750b00d655882844ae2fb9ce71205d0589aceb1576f571cbc9a5c6a3f27792ca93a1e323e0d8a1ed3653eee2ef24922bf427dd7f1702ae38b6d60d86f2e794bb6950b92f66109552c61edce6b83adb8464e3e0751732c26f1b58749895f1e3da9a5a6afa78a2b2262e1cf983235f891dec3a5cecfa3e8264fb26da7b7419d88c494f7c92b2f37fd8526ed35094888057890d7e23690c3a003d063081e5fdf5c2a087eeba6202f4121de650d9b49f31dbc06b922447f53691c005da03e879e066c59094202980cab722122857cf3be2065641a92f520596d85d6a31644eb0650fa24cc5b0739c3fbc711a526e42be2e8103d1184310d57a8b453fdfa0333b7d2daa37a0b2178abce01077ab1e0296e06963b152fad7d3c620534e78e45e54f2aa71e3beb8ea229c8d80a553be89bd3395c48133696632383a29e95f8a326d2118d1abe9feb6d434412734c79b113ee8378da57d30f9def936f6f04f34fded1d02e36803e12dd103bf27b07d62e00b77063aac5ab9cc0ae4939940e3d435d36738da008280e034a4d782ec8b4fc376cc583a31e9ff9a69afe5500eb6437f5d75dba4a9d841a970048248483768b13e5d1828f06bbc1d4aae3352d333080309672b793a8d311aafafc10ca91bf7c35f5fad02577bb6b2f5c38e188f980864eeefa0e1d59a6587d6afff863c506492ac0fb3600ecee786a0ddebd6f8ebf7459de35065c5b8b6e8f0a136af05981d9e6390185b93f39736973b56f85ccfa5924622f7cdb95a7c52daa1321c0e52bf967206bf0dcd8ba25361aaf3c2021f4766c136f9b3e773cdec400ef9b51d294175d07df99079f495d607c754cf99236b523134821216ffe461f5f2966c4dadbcf8b9420fb2d7d46e8f0addd562c19e50ec58811c43cd24f7783e7ba35cc6e005234c75d0e3ca926281c960cdbc8953131f9a84f9a8bc63eb5ab557e9771cbe70ba7f8dc1ccfcd04fe5662be91898f226f10c00f89d3b17547417f204fb2cb390fad822bf2867065baf90509017447a0bf4116813573c93495c95ecdc08f400ce7182e71f6677f44dfdba000f7a9845cfea8962e6045db29620dc7570fb09ca7eff5d41ea8cae364bd7d3f14f678fa8743d7120651e7f04abff3592b36cdbe16f7f44d6a26f1989a16bfcdc5a5179362f4f097657fd6a1e711c96dac7e6a3073c2bed7fb20c4c9069cdae98c1a9e616eb5366550e38ab6ff721fa74cb695976f9cbbe6a5c94e62800b857a799a1622adedce2ad986e6c007f6675999c5bbdffb93f880aa14a615aebf90d303c4007058fbc72dc3d440ac379055ce8a9cc94eedf2c8fc77611f7af7d8cb3cef8e865c74282a607948e7db67659c243ebae8d3a7cae136b3d88c7e11e4656b8bb79bb7200de52340ffa657af00f83cf016e458c98a13127e9e7c2cb34d890ae16e774b3f36b0bf9bc9a13105bcfaf90cd8387416fd3575222303706b20325912a0133e6153ebccad1a479385fae52cad141b59067f2eb0fe013db696409287dee35685a2d606dc4c15648911597a4952bf287120ec45cca0725453aa115383d5d4aff0c7ac7fa2f9fd7af23c09d98f85abb2a5c2ca26afbd8cd10972346287a2b1d5287df0e4e0d06e82b0b952390f11bb05efdd76a565f68acd93a9e4782a6a1769c9bf8dcc90b5f26c8ba500a039d00be37e6e2b7b5ad68efc212800508d561a3989cfd28f83ed741e85c3f2f0cd92e2eb37b36052fcbef2364ad7f6ef344b26e4053db40814d4ada15aac4f01e6ceed7aa326f9837fc302256a2db0025834375a2b61492d64a1b18272c56b54cd6c78cb9be295bd0d7f93faf6e94dd7c2dc8d98940cf3aa50326b4472861677b23d116fcc519fa7ad78be601c6acaa5de462d5d750ecc5f862a08fc1eb3ceaa48e180674009bb485a728feaea71a6c799c102c091a4af160f7028501899a5c672a44466e37380dd627a8c378bacf7af09afd1fc2a222be1a84755218571cd0b848e7b635e04afa2fa9a4b6c21ca41e6f15fae2c248c4c53b2ec63b3afb46a2c27c4180967f8f397f3b680bdf99f0fa7bf0cec819aeecf0d160e1cd8dc071f75b09cccebde7e24f59a3d74dfeffe97bf8f7d6ae63d5bd959db4341aa4ff66806b72060d2a6292832816486c6de2c8bee372cf8384d7fe89e05319449d52059f6d6e44d91f92e5e3b9f1c082fa24b6278c86d9cd33b3a035c4f2fc161802667280fac9cb37d4d1a212b67afd0996ac121959a79936574667816698fa282e001274c090803e3b8142bd69bc4cc845b3da8ada237c09626e6cd672f1742a74e6077ca92fe8aaeab70060be8af61736bab455e84beaf011ea384dbb69a1ba0b274a44bd41db1c5b905a037c2ca7b208dc4aca9c367c31e61504d8161534eae4c23487010891a7b3d6e1b9d5cb5538d5d148d468267e4308b974349e425e5d4051a6f8222f863fa939aae13960c26260141a1cafa21ed7143a6a5d7d5f9680a774fa4d0d0eb67ca5f58408c6ce371fc6e4f92d2e3cc833584c893282c39734967170b23e54a70fd128ac93fa838a84196075804616e0c859e3ab7b6f2c249f4c69c4f9799f81dad710b68c4c7140d1c7cb4fa68d648ae17a05770f59cfecf9d0fcf274cf1ba850f20d8e0de455d45f06109ed572237f30f6a1bb0cbd13093bf918015ca5263d93803c5dde4e54e61df45ed13a8aaa627162dd744349bd09668df7ce318b7b1e4d33a38225a77ce0a17dcdef65747b4e4d8ad5c2f7a287fed8502e18967a26bc25d86b5efcfc47f23a1b4ef62b3c92962675e103334ede77ff21be243d051c83e746ad0ef13498145222cfecdb093a79edde7558b51d77d3e2a28962d6df0faec38b8e0867be8db63c8d80a08b903da9be21dc311a61753ae135ed2e8f8e36070f910756a9a1b9631e4c990f383b069de9716e24e7788656f3d4818e3ff37718f760b42a21b17559880806eee50941c48c1da59969dd3899351de435fbe6e12740c5da132a82f7fe5b6111d85088444cb6275eb48399b45fa6217ce155c4623a462a7c5757c153e26b7d9edee4a9b7fa433264731c74947e36a36f088b19a0e84157a12e7bf63010e934c5b43ba55a391b64320d3ac177f9462ab2e8054443b78688d89703435c01b4c1c6b06eee1aae2a486c382db49a89113048f5e57f2e527f3b968bd0725a2c48021a4ce741ff68794ee1bcee692ad7c32fe1fa7f268a73d6f40d4d0d3d6f9197bea150c77e5888f3f60776a66e0ad68b7ec437325547df9902bfe691dfcae36ec477fa0dda67ca1d7283a684f42ca6e00fc0e76ded48a6f41041fa10a04540e43899ef869a7570e03457f028d16116d10fc53cec77b5efabaa2b5c6e080adbc3d688e8e3ab834821db998d32e0ee0f87eaf2da9c54c9a0a202824fee8a66dae8d2d8d7aff68caf7e620656466a4844c466b0403d2154e9a760b2a7cfd275d0039b32c115e9d95f6a600148497f4ea185bf0d460886653f2d00a78077043a3d4ad90d28b507320ddba31763d2b947e2856ac77b42b30d67b8ebd44232694bad9a7c744715cbd6e1f17995d6e1c8ed36b26794a331c6e0f09c47acebdc7f355abf7cbbde2041723b0da6f44ea1f529e8984432db9cac6bb16c59d5f632f73aab8f7f3e3ea69399941252342a0f99335dc6745d17f527ddcf38cbb7444a584cf94ca9b0aaf4c76b105c5a35123d90d116d16d383df65e79818e37b1facb1552e4e11a69d01add17566b043cf87a87b5c599d3e5bf00577b9f2f5475f760c48742703e995b9b0bf5d8c6deaa8acd264e4e845256fcebee2263a5af4592774febf5005a242ea12be3f8443e8413f2af4f8816a2cc3bd2366e8a9887eed3e1fc533e27f68192936f4d6ad4563b9e1fdf5565c53d7a7482ffc343fd46740eada9ed6bc7e49aa2ed9e298b0c7394ff774b8fa7da846d8aaaf107131c98a3fb3816ecb17122791cfaacc435e52cc4d61ec5ef1b568e7ef1e1f9d3d235c6479c777ab8e1496b69347e80ee6a2ebd96ab2f4b4ac404976eb9764f199f43b031b84651bce4a8fff91f4f8f356bb7984b9e81db2c6209c08bab6606403de53ac0dbf78516d1acf724db346b3b1f4cd6b0e37c1acdc46d2e87b7089077fba941f1e01d05d79c1cce395450d5b9384cb38cb68d8346f6cca4600c31f0d2862067967dae330c3cf37f8082dc739bd8edbe1658def53ed23feb2d67e4eb91346f1dd44ac5de5d915551ded332761058a98fb74538d121651745111c1b7a25f40bc79ee33198cc003b801a7647ec33fb5cd77d2fb5c22748c6b5909c3a109dd0a34848fa8f1daf06f8ef57a0787ce9bb31e2fd55d8076a4f44251d3d3861c021e7448f126fd15ad8dd88b1dd8a8c3554f6646065622cca3fbd82a94e9f0f6cf4cd31676b91189917e06d89dbabf357be1d72ca8578b0721b304ae104f36fefd0b574628843200172b384d3cdcc132a6894e2eca4590b2512f3a3b4b546da14233a54dd2819f9d9049063cf5f33af9b2aa6f19452bcc2804917a749f12d465a42c4486932404c1061c12fe4bef0aed6c1489adf8f59aaf82af66e1a2ca78132b23ed562e8e1db6f01a0205a3c878f3c03c027b9ee0d4c1e89df502b4435162be6019b6fe862452e56fef0d22c7b3319328dcfae8cd8564912984ad72848ba20ec1ee16b46068222481b467548e70affc5e2813219ef30ea5f49cab23abade4b54557742157afa808b417d0477d85c523de15bd8370a8df897f21ab13f01f102b1b8bea0377bb089fb3b84dd3fa177b14679cbdd5f6bc11c8c515c94b93c020fa09d22e56e890ceede00626b3d381824153a2a393115fd8d8353a0a66d9eedcde16cfe61f4d61949ec1a8159aa2d5686367efa55a553fea9487e1ad7751cebd67506a4702ca43dbf886db3564801d585fddfc7c0f640302899fc5544037eaebcf0d2229987b99607a4a701e70bb735d8454d4183e0d8b6419c5ce95364ad0017def33e7e2bc27f8f152135f76207bada31aedd2443252a8c8a72565e48329fc457088a563802d5f7c4b8c0243155c669dbcbcc9a9e9ecc8a9515b8e63c4297b59ed3598ba076e4a4f3ec05163efc1affae3daeb3627c852d74f0fef5f56200286d967ddcee9cb025d7a5f867068b120cb5278e0a742d6305cce7f46446e85b77ebf19f67c079f36f25f195bd72647016ff976fbca4e99c250fdcc8942a76f975df5667f6dddc320598b810a9cb47f44cecc8dffa7a2644cba8f5f04fcb9c4e37358d98a5579a121c33a59e214db3a41c035f1babc78a69665b9d102118052a16156f09a121e0da2b32330a57aa93455c6e82f241f8708393876c62395abd9a23905df76b0cb695e30467968312ccb14cd335f2bc8071cf8776af52649a4d610ac05cf851883c75928d157ff21d93ee86313b34e2dbf59d9f673eb6dce2b13558ff048b18fd52f12e63cce63d5885db97f1fce61866d8b635cfb2f2e67040b1203c965b434574dd8d2bebeb3a8921cc16dd0686e9747934dd4ce350d0162be74a5aef9edcee422a2a174577d56f7e4815c45e2257bae6e03aeb12ce539cafb0211d40b6711bbb8cace2100314df249383ed609002bcd10f04797c5733172ba2290544739c6e5f63909e02426e287d292dd1a85f02bded57ddc9ab022e8c87638c3b59fa37aee4086db6559a2cde95b89147df8c753d9e1d08b2ab469335483d1b868140860af9bf850422b2caef5265dcfddbbc72bc56b050610652689867acd45dc62ce07b5679db68cfe28e9e6eef45f114d5c395e341ca0789027d0d136fe7ef8e108b84f8d8eb7cda8c317cd7759d6de528d2609b3bae78acc65e7fccffd12131d70533c691073cb8a8a69daa3ff8677858a6143fbf37f25fdd1a935427ac6b1b8e6393f8aacf4020023f2c5b372973b1ded3e8a2374faefed43e541a486020a7f4f7790a5664719bab448e04ce29b32e52c5d5ca9bff81244cfacb81808f1436282d3bcc9746c9f96dbbb14a63e04f0532f6fe69a4bd6dd54f257f9fed14a5a9df071a84052e1824befec39c25ade3735ef11638181b0bc46df7f6808f4350d32dfcc7ed7be65949bdfb2d99e22a80bebf5fd2d008eaf065a0f76f9f304150216a1c712624124ee2e601035ec705be9fd27788bc1689add2ef79909113ae1b72e832f63814dff88d0ee47917eabfc87e45705a3b9a446e9cf27e2998560d65656421bd3a819b63722d9f417c87f87eb3087e547e63665b09d391bb8955be03863545790e01d30a3707dcb1f42201c9143769bdba25919d2afb96166aa46107a6699ab36c5fa1486fc48039da5148baecfbe9cca57f96c7d6a56cd8a8b1ab57ef2b1088a9c9d0b29d23f1448253847322170552c6a6a903090416920483c5b9d959b288caf86f82467c7006f469b738685dcf42335c540aa2266fde228507c8bff8853c8bad79efe35801e7fac298a9b91d99e9205e283664f2d4d9cb01b27ec89a6716dd6d9442a6520e770e25406ceebc3cff88d4379299db396f3f1be606989659709e3fe8b83a57a5410a031174a78e1ab6163fa9bf188cf1fb3392aaf7b93c8c1725c339cc063050aa30069302baf9bc445ce84498c364f1eb5859d166a371e95af477e6b15cbb53dfe483e2bb90c8026f36702cc13d6cc6d26bd4ab06301896830b44c403450086a3413d744bcce87ba3ca55eeec3083a2027db0aa8b202a994d7426d0e911814e1d219ee8745b24844dd34f575a3c42eb3a3fd9c126801bfb2a32699d914908056a431d7d10ad4adeb6ef8c32615bbfcac01328655e5eda0bf3e2277088c488f89940774a521ce28cffba32ed79ba017d72ee24894de4107666a3adf860e9815757fffc79a53bf72eff99e182918f8f197592508323be803a6a076c6bf51accdeb3684c6478bfd0d14552fb9ae992878d7c2d82b38045db8d47e9ad5854af360ed994388a26f9c4ca3eee34513098acb3c17c6370437f99805800f25c7094af5f12049bba12d6e91afdfdd6b2a07425b81515f0546a5d7e1dcda3e2502201be8e1a9c8ec71dcd24012a9e81a0c89d246a0283e0e1905280697446b2f171635b3cd11ce51b40cdc5ed9e97d2c02eb845323c46c45939cffcdae6b41b1122c32f810dc466d3a57dafbfa1bcbe2a57dc9ffefac7fa6b7adbcb9918111af5c0a2a8b51469fa91c821017857aa6f13d35b751d2d916e25481b5a21548953efa4e119b67e434196993cc8fb2e2c776017dc4546748d407915d4138a6b9cc205c580c6ebdf00528babdd8051d35412efd43d46e6d378786b198d1f5efb7c5e9af5008a7f3f6b056b2252bac71b979a57ca23a92361b46b082096278a8b5ccc9d67344cd635d2f31f0c268a3159691c09f5d4e70b01b897b3a85eb03242fb7623f10d6d89f198f8f3705ed4c84f37de50136c3773da1ab70a3ef1062c1fd101305176c7e5eb9b68c26a1f5212fefda4a98e78c43d35f51c74512182b6dd8a3680f0d4ae24ea358dabdb0447806e2b785a573c4d8536e7a2acfa5da023e3e0d300d13188b5f57ea0158edae419601b73931ce82c03698eb22ffc14b10e7c4480546bcd300ecb03ea889ab53a14bbf625746ea4f93343cfded836c9ed468b3ccafc259294888247ece87f82f31bbbc28b8f87f910e1282360acf9721ea097aee4c4963bd958f03da64983a722aeac325e5dae281fb441a96a55d24065ea8d7d42d4f80bf55b493b103f081f21d63e72e0a1bf7f0ec41f92d79b190cc4a37aa38077f555f7ef801cfdd39a2aefb65a1699231781004654fa9e08030f35efa6d73387364c4ab3607ff3e26ad80a4a81115e744d84df33402a2495e29724e13315a028ca8fc85f55f981257a4ccaf082306106333223a0c9954933e4abb443ad1771d7dfb1b6404abe3d9f55c8b6e7bb55647aaeca4d5a03e68049a6f066f7eb16e0719a24a01ea0c51308261bff4ae5ecac8e65b06fc226947ef604690971d96ff90e4e2bf8efb4de533e9ccf9edad7c57075ffefd095b20c83dff72aba91ad00277ac8a088840f1582bdbf2ef77a6dbc3abc8cecdff77250be70c3209e88315d4380f826ed633406e3b08933bc639249496b195d1cca166e5bdfccb773881b9549651f2d66df66519b64b91476aa5771cdece430ced022d7042f660b704f40dd58ad80bd65b3990560964086b0e16b7f825b15e80f355d78196231c0d1a104f6eb7c04cdc360a2514845e6e79f84f2d2bb68f484b87896b0a92f5dd2c8c276124db8759f3b66784e19cc01987e4efa84d7a9d6ae48de34cd087d154e502c25217094cfb600c6ff3273e34dbbcb62546eef2f6daf66335523ce814b402f8d2b88366d5c1675188b4286b242aa97f5988da767414d57c62216d88c0c30fcc50f80fe8103113cd086665214d2530ed9b8edec2e83027e99028ffa829244af115b62a9d28881d8a269a5bfea0f6658eccd2d1b8b95214e5f0a0035ae326b1bd14498b8e338ed1daa3e108f061884685350beb42f3ff091f9e176ac00a29c0f1c945069ceefc2b83e9a22336cc721f754841e14de150981e2ed3a66edf9282ac69992d455c8e6510f447870ef7e50ebd72ef6f09dde1db43dc9fbd0e0a233bbd37588b67724fb18cb3efd773732bce91fc6d0e696871f8bc3daa5534c40c144bdcc1791e8533f485d6ca679a6d3aa74390633c10d868634f64ea7c8f5358992cd3941a37653457881dafbc70061084cdbb841cf8d812244b42acfb6d389b7a9f045f2aa2adf2712d1b8467a552bac7cc525d19892d78b8dd46fcab6e3a0ef0c7cb38b9726f8f8ff632efa371f36295d3602871fbc2a044c64c0f1297e6baad861422fdc56b550534c9ac8fe862e9bf331e5b1e8680ee4d3b406ea367ad00930aaf00bcf56e1c1917149db543e2109136de35f11fa23d464d1157b9a5d819a1426e8e07dd1640fc5d7f74727dda01fea7f8b81bf94886c04fb214e743f6a8d6550212211ac5b4d2eaf5cf2995d131e4701eb764764876f1d8c645565684be08a1996dbb5f477313c0f9ae9f276c1b05fc23d8a26a03999660b2b4d1b44a9e9db26fa3b262e9e6024d21c4b608f0acd0a471337cbdad56d40ba62e87ea17595c13f9144e165513f8aadf53469834b1a5310aff2a548f5b1d4aa9cb475a2d71ff22d78caf1d839ebfd7b5e4825bc009d59d645afe4c2eaa74e4e5a8326694cc6727c24769d4614fab6eaae1a9f6211703025727e6591ece93f3b41911d3153d23cdd8411ef8a95e96be6b1b4e7981c1fa71b4e910c47cae67feba29f1e116a69a450abacf395dd0d93d76315c54214de12614a5f934a7b599df5f2a8ffe9152eb5fdb2354fe241d34ead7f5112087e113d00a712d277a0738bc5c2304e2015ce96fd974e1ac061a91c0604bad8779ff22cc7a44a4302b2ce9947c697cc64828e60da237ddce85a464ae3f130c68ed63c4e36680ffbcd406d706e228bb2d65bdf71f55f0dceb4786db484b7de07330eed05052db62f55b0fc41cff3a5ed9ddab5ded488999a6edfcb0807704d46aaf2dd85ac0947bde7a4e943d909edc14f6f66e3608be10a236b53716578c0fa230135cc746657028b8ed61bc13b8225e266d01fcf047aac639d3dc00bbb2ecd7777043458d9b973738c8a7ba5921108baeb0bde1fbadc02cafcf702869b03d21e87a0f55211fa541416826028c8242d9e9c53ced493617738ec254ae6baddd4032e2cb47536110862e712b916bff8c2e968fb663dc42373f66f902e8cb2dfd4a8f3d4cb690327cd5acb134c5984c81b380c0740e26397cc12d80d96d2df97b5d07457d130c460166e5032a61bbfea3f2c431c1c8fc745368ac46a77d0002e8307524e7966b53ab400d74bd7832bf5a4e95470ebd16c1e657c47e92c1ac95cf13632e48a14a3768ab1d73c1d2cd8066dee58ec51de14bf3513cc9fe537fccc627c704a1533293763f1977d89cd583487533c1671fa584a66bdd7f4a03c96de77bf277fb6d501ff8d691e94546cba3ffcfae030508c7aaeba538870ffe7c5811fd263ed21374ff010910ebeb8206dffc3fc8256224031b391ee94af5fb60eccda699bf7a833092c747f22c388bf0be2c563f850224f4ef8c8043a0ff797bc3556e43883edfce6498c3e92d441f9482576891745c4892071c62d03d19445e65ba2ee2707e37943903de29814b7ece0bef65d202fe544d06bd79563bb335893ee33ebdf85c608dc155c296899e4e8ec1b0b3901eaf833ff3ba64da9565e3258c54e227105beef443bd4a4803ae6b43dfaec19a54329eab9243ff27acd895f58eb571c89b37809e0a10ae24a708f43901c59fff6d6870636fa00abefc8f6284b836fcc1bc08d3de47f2db919bf61c93892df3686857912d3a4bb9f276de2e1258dcfedfe7514b48b037558977c0df88cf18e3d5072fe4ee488c26d9c88bf7a1b663778524a95c7cf89290c13e13b763e7e43d68a015fad5c888cd755fda3a62205bfd487c5885e868394eca45cd7f88715e6a4bb5becdc13090614912cca970b4ae3231f6d92b668a6e7794e39378eaa6757c2b31850a21882ce874acf4a6cc9f5c5c4bc87eb489db015aab65fa2a69b99644fa7ed96390a78b78c37cfa1705a72feab7a61a92fb0587c2b3547ecd93566f9a4aca7e211d0a125cb166d468a73310f7fa6b127f734d4be1b2039e7eb27a3fbbbd675aa38f561807abb4d6f422268e88d85662490b6cb6e6f554cbf5e1379337918dcfd0ac10dbe0967b96f7596c4c208abd5cc6fb3193c8a527188bea5e61e3ad8dff51fbd1ca83559b752564bf044913540fc9b9ea637c8000d628875263c8b4a6e57134da19b5321e5d5c92ff21f4c01b628f4825cecc0da147690d1277277b8f346b906c9231615db58d5ab596ed69019f5622ae2ae8197ceb848ddb2d2cb6565597d527c57b16e30a96a02e57154ca7c056fb52025e410837512feefe56b92193121587407c49940538756e15046c042c1d510bc3d40326326549eac7e6e0f0167297bc011dc1f9389b97e37a66a6760080e668d52ca2251bb605e3992759eaae6dad43b2ddc80259fb44c44c553e4137706cc5f90d6d176130f5451256fcf35bcd4905af6fc16825072cc32515718089089e4f3f7fad4921014aa2efb5df64c3be74dba419da216c55f99f92ab40225f85680df29d8f4511e174415cfb5d7d7c7fed38d3ac0084e8e31d1e58dbf09e55e1efac8542246c0b9cdf3bc9086006fd5c04651637a660e066e8eb8f0e3974d303572e9fdea688c79487a65e485153d3c2128ff95ca1b076c9a971f6768349eb050f6b59ce3c00ec617ce39581841c378f4e3ed8f07392065471db74a00bb0959a722f483b59a68964ea29f5d372bd764faba6768cecb5df78b2676e89cf5ebce54da8fda174f22dbcd1854b39a61260587a5b2b896ed38ef7a355004fb4d730ea69e58e95c88cb79d1f3602c03ae0f679a1dd8481177d1d99d597ee8f085ff77415a7757ee935742db638f8697014d77fe8294024639f9cb402fa614ef4280b5e7085873a28a04eafbdd88888f88772b722e3bee42cb3fd69df06923e5bb3dfa123b078d802d78b52abb314f9a21dc5b4624d1b50fd3fc34897d01621ff3f4fced9d6d3bd03adb1562b162e60efd9f6200e94ebfbd79f47d87c22c49b93585e39970b066c8aa3d7a906e05b9845034ccd46bb1491ed47c69140618609741f77f0bfb560bfa174e723e96c2988be39f75564a1d55b0b3354ffe0d5dbd824c9a663b695c282d9309ee3c877e41592eb0b8b80568d94fecbbdb17ac7c7424c9c2390e26c2d6768b6b85dd1266f4e9b5e1513332182eeceb46f4623e6e9667c1b67c63bc56a61a63b7175d0441f7352607a143a7c482ab3894aeb45332c32923510c5e97d08f2d5024328540dddadff66849a3ba8b76b88b8a70c4c2c420c6f98754c60c8eb2cf3f1c2d7f23269942dbc20a31de16c07cea5fffd0036b781e067e8c9aca9cec3a5586feda90329a65b5701275b044c33e79f689914bfb7f5e3521c7f834405861cf3ebb7f0011bf2643174d2195a33ee9af0332ac07c087d01a601970f7a67f3d22043100ce38de5839dfca7238db9af651498ab4b9d8afdac64494bb4af0d4a7fb7dede20fd4f45763d796c6479ea04c84e2146848347095d8e57f8ae63cc6894cb0bf7cae8b19d7f746e9c53b1a5c8facb4f61bf1446c7963ba3333954afed499583de04ddbb4b4111ebf09c7604f0b1fb4b4c6270822bbe6582d33b44854a7694b0be860fe13f00965ca278f7d78ba423bae11f084551559d6c72855f4a2e07baf5180fcf1228cc88118c5e924612b38944e885a7109e54150bb8ccf071d1684345c84c6399a98a067073d38cacb1bac9d3f31cdd477b5a50e4d7e377c57fcd70c8bcb294b4f045edeca58ab926090d8b2b18fcd88cc26f6243787bf44c2433b87bc43f798101732731940cca239035368da710b51cc6bdd44c2c541e1a6a637c3c9329ff246a059fb937fbdbf9e419a438cb4d616efe1d637137855516d26c03e511d791e12ea9bc35f20d76f7c634855ea22a89f21e371e40cfc7af6b5c1d9dfc66a8db10ba88e1a6241c3654789e0566fc5eb2842e0fae0b2e599ac0106cf2cb77a1f011ef91c03d4b667da9e810a49f652e6ec998264c1f9eead99958e6cfd12e28849b6e7edfbb82f00721fd5f65944d72581a59a536a0284981108d073806a572eb18a026c53718d3960511723cf5ef26c2dd4c2037f2e61bee3bc545701268d65424eb8f3776409d6850c83a10192913baa9137287add43becfc1adc3c00bae35b29c3b2db3a2222af127a9a54b748b7e360b990d635a712c1f2bb1a426dac465803e9a3f550e062aa4aa90afe3b0dfc4895c8b08032def868bef6b9bee681b8b73bef6ce9eff4615efb95d6085862f888cd02c5c912837961f5c8677ec4e3247fe8dfdadd1a543c892e687e703bca2ac0baa9ccb8dc6f137186895699664c4e7dd35c5a05ba13bd628ab7f6faf8fde0eefead5233a9670270135358f27946cfc0af815493b23747e5d033e69520df3fe9264662c17b191b5de91611a87b66f48d73aaa8c20ac678047fece79076ab58b56a055b61f686cc17752f9deee81c1059c5157c8e443dd29f65ef71c0697758fa937a64ac72ac9903feabc84ea33a89e456d728b12b88741128cd6b1121a218ce443feb6fcf3f79ab4479c2764db81588a6a143bcdacc6ebb87787af0c952ecad79a7e5e671abe67a386d7641c3837663a747b194469c61d46b9a2c2f2fd3d4dc3773f9625bceb2135819bae572a7a575868013f1536c20ee211a2ddee62ced1ee1de9d20da420dded3d8181b0fd9eb8b23fc0fab7e92b473061cac15d36ed6b4fabcadc26cb98c868f6d55aeb0f1521760b610c8fdf7bebe08e003fef837d6eff4f9dd4125a209cbbfe214c0891b4e1ab8fedc6fec3daacb53ede245a64d1a2319240e2a24fb12c0a495dfbd2db58aa443b27ad7f065206a77f4427bfbc220bdb149ead14f5183a2cab994c38980e1eded6270c6c58129a68b81ba39089de664b6b7608351b512db78ff4764ef95561373a28c0a51e037777a34c6b4b7ebcefee9523d58b9eaa6fc57e0d339b4e652fab66588ecb2c2cdfca206b1a332e2c773ca6625e0c2bad5e7559f6eb4af5e5d4aa8b6865c63b5b68284546bc2cc79e81122387520b295b4d2812d401b83c96cb492dbc6e266974fc95188b919cc2d8a76d498ab1a3a4442beea9c4c1a2705ec8507a1cbd1e992ed14554bd25ed57ef71bcd1bcb91f4e4c877ed7923b3b47230bc879513ee13af8c54fc8b3e6e057acffece940d34202ac156fd486f3d30efe580ae315ed6653ca521e3d0921c707c87e4b74e665fe85937c4d8dc6c2355a3abc06388ff29becbf28276debc18e2eb1f1b7c48e85fb79205ab31fd78098a0ef6c39d7acec477da3c7402e526c38ff9a0d0fb3458bca4b9f3d6153bf0b28b7ba7c5f3f0c9808729c1deee967d47f995b7c30e2f92d3ca9037e545fdf3e769b6f1a011fcdab436ceef9d794e8714319412b525df03ef2e5313e86c0f77b03fc94e445096de0e73a6806e3ae3e591b476b50a2da9cdceff310ec9a404f39e203b506f334e29b912f4cb94f997110cfcd94225df878240a22b72033de9f9fa4ba5840899ff3b8aa9a3dbc2e7b37b08f975655697cee141a5910b5f82fdd884f3858153596716001fcf413937e403ef4f4ea8e2c164859088dfb188d549da87fbae28fb08bd53b5feae1695d0dada57861a8bf66a24711758319f247b7e0664100edf91435e06dc4d8d9bebf130e099b8a45143484c2cbd3d541856a4dac014f7f0c15dc706004f7fe32aa57aeae0972b2797a9aad57bbe7e3abd8dda9c331c78461240413c73f46a74ee49c0cd0cb86f24c67eef892118e1864889b70b147470884f97851dadfa60ba25c905ec479eff73664a0ba858af963b46c170ffb7258850dce4ed25fc56b33b5b235628ba63aff9e7b6572c07c829538defe91466ae56a67b9f7f1983baefab0a2a542d2404096cf116ade807302509a787fffce23790deb81f9bd7136cfdf3b46e97251f38a8a6fd01dc03a964e6c0097cd5cffb3eb8df1c1a793711a7b4d145e248d53e2cc6c780a245b12467ccb09ebd4ddc6877f169a53ff30aa5bc96dd6d83d97fbc545deda6471f19ed323a8a1eb98217566bb5439e9513da0f4b28ba1189712da534b08f941fc9dcc642252c7317589a7a1c484717eca766ccd78d631bd129f077a65021c0bdc04ac7c8270e16d84ed60a060e261eb6c659130885088c8496191f9787e286056fa6e64811d1f6f73d5480ee360bac347afb7e6407a8bd2bbccfa31b7e449256214a4a2a34ab539dab3f0b0f332f8d647d110e949864e278d96bd0f4fd887d6027a1b6b1910cc9b54ea9bae736943e9eb936d47eee1a2e95e48f1e0d08f35a864b4219035d28509bf7573b85d183ca065dffa245c9e67b0359a54cf54eaf5798fa74d705fb0946756b2b285c481aecdd71ba4ac8f6672e04397b4f3349c7a31425cbda37a7fc8056fef4f3e5db810ee8719ced69d6e8bc1a96733f0c33e24316aa4150af79ecca46a80cb0b5733053eb7773504130e352351df690b7d83456d891a164fc08eb2bbf5cd6eddfbc226e034614c05d8b7e92feeb317a15bc1f25d8e336d6187d9adedfd0be8e1e3b8b67a5f43e74045f2a987e5d23ffc5cd4df50c1a5310780804872160ca64e13bad98ae5c8cab897bf5da4e989d2fb7a41aa4b980fcccf11e39c31c37aa58a779ba4bdd627dfad900c4e4cd8464f5b3109c84824890c81455b68a1cd7417dcbe9c69367a2aa117a20f458f2f400e3c57d1c9c0de7d170515763b90cf3b7f3b964a6be541c05efcf853b5122c6989d0d48263de437d4be4e33170a916fc0a353f86ff7309e6571b883d4e71cbf72015200d2fbac44550733f8d79891e418193121e9d9187754568e8a8e68b19ff4f8a50642959d1ae7cc5c6364588af19a6f2cecd87c0479733f8a5578da2ee26f32fc11ea798cbbcb7aba0094db42fb3773ce45f115f741b4f3fda7fda16481afc82e9c39e8cf59be52b58bb36d8ee4a4622e0e0c66b738a3b2522464331f604225f6a77f001361f50780197d5df05521f81a6e9849d46ea136e0983986023a6986847f4091af92dd36c68c7a93991fd2523aaeecbd1a2d2a3e512b3e0e1d1456d4cbb20309333542ee6126c8bb152167807c27c1880ca514624a1d8c3d4267ad695631b7cbc0bb07cf493d3964fc55aecea8306ba2d26e9e86637c114a48777cb0f833d835a8069ce4135e500ca0de79d0443812782b52b27255a8bc1189d26bc1ef1e4105d806bcf065a88c243fe288f75e901df91f2964397982121a4d73d8e68eb56f255012631c7036451bef635474aac0bf42d9c69d0971c6948ec0bc6b45b32045d7e55bd09dcc1dd026fc51a6d5fa49fde49b33a6acfec0d36cc63141486479379a3835a0309c170ffb097d22b5670b25c4e0c9cad5cff72880693393c433215200857839585ee0c94bcf5d1274461cd0fdd07dadf183fc43c601ffb05afa8aa47e0a600e08b96593c1792631bd08210ab3dc207db82ef802327f6c85192007c7659f4f7ada9384d5ea2b73dade022124dd116401dda902629c93bc5e63bd879ce52f83f5e4bc72c948642636db0e236a816c37ce8fe6d85c4994387679456135097715e36be9ee5d9250cf658955bd4b0cc0d2c6182cc3f99a430b13a1c9ea076828ac773d95703e00aaff9593f41a60abcf5dd9f8c6105adb8d65f63196c0a73c805ab786b27030b4e5c3530b6c8f3377f25d93ff6525aa56ff24c2e35140a5d31bdd9dfb9bd2802301aaef71e17656bbd2c02c24321da62dc93e67f346ef83f883bd66d53f001686b9c3645825ebefd48ce6b0ad39218488c16acf2129b8080ffd45fe5c1b017fab6152a7812738eb6119c585c124487d232f70c0c29bafac9bbaa9289390abb2ec6827a11a368705fe6e30a6d18c22a4c4317c8623192074359694e6176c4d76a723ca0372fb783d2c492a58718c711dfceb610bd9045ccfb9bec1ac4247ca0ed694a489738f5b7a3247cefdcc1cf69497eecec0b547b6f7db748d72ba7867c6e23e003b2715de6213da5fb8051766fee74d8ae857afaf7840f72d798ddd418adbf6b005694cb069758d84f4558f4872bef8ed049b507a06ff599cb5af1e76adb5d3cfba83b39a3cd47400092712ce6e8311618de97baf0c35aa40d8cbcf4f9d00f43bad93b702f988f6df9fda0f7fb7a2a748dedd90767415522d01ac42a31925438f5a500b4b230664c1abc5eb8014437dcbf4a126932a9cf66a2cd72af0bc2f5c36882e90788a0f1ff8e86cb760a2cdeba6aee41e3046901f91b4a13cded433b5c7a2f6899088e5b52bdbc0a47c35ce1baf7d16f0e8c60ec6a0bf3339b62ca09a9e248cd4b969d4b8ca5c93c1a1562d7941b8d5320d55d296cc989ef8f5db6ed90cb51555d326763fdd6236155be1621dbc772ea5f18f4665cb5da9cb363c455d2a0e48fe7d29b99937b4b750307b421729dd5b5852a23bf18b04e44d97a7cb7b2ff3584d72f489093753a4a583e7a37f32ecb334fef06c72ea7917548dde3bddb848849a94e990e1218a36b47bb9609c5e0a54c0bf9b779abbe7a6a2e29e9c211d98eee98168e2b54e1f8a8022f60c3b1747dc6245ecb538c1d6e6e2d4cbe37feee14e473c295784f9bfa9fa62f75884a7ed7da9831b6886fcf3a030f9023f9cf47f4893ff3490dab0e5e40b31f0ebd7a274a850c03cbbc6ad78350403051ae086b6cb284b05f49ea7098613c13aebdf5474e5f760845e38c0986e6f69864e5e6336cc3979baf1d18ca59ad61569e63f6b0d703d49e33164fcb4325c6479fed8f03e0685082c59c856318857fff5d0042179d1aa57b65c2a5d47354bd2795858dc1c8c072d8622456292ba16994f404b0857c220000c4ac817e6aca2d82d91a9f8dd2a7331fd4afa10bba24d63181b2f71cf99e603d19c6c6536677fd49779d219906c5d59b88b6a929682197bcc7cc2af3ccb6ec2cbe880863d9883b56964be35de3c257bf703d21ff5496d65b0523bf8b6a534130fb6dae51ad83366e2e49b133324d8f884c3cfda8c679791a7a89f12b29faf6d62c795ef823e348d101da4987b12e9eb10fe6127d1afd35d83cb95ae0f0e2df325702df94a732b6d0a3d81401192bcc397c107eee98c78d96d86d49aec2a7cfa964896dec58516ff7bcb45f3780e98f6d1d563dcb482c5dbd8d8b9161ecc459a133d91fa54c32ea73fbc0c72981529753df31012f9042542c4f3919fbe7573a4fff967fb8257ddd00361815940106e1800814608a25880110ae83680885d28326b1e9ba68f1449355c2b417f01c785516db00091c8d9a361bf5404be985495d5e48be60950d049db4b88f036091942a2d52d9094603c853d49942f06b0292bbe033cba942756a4c31cd98cd2396cd37eb414611333a4f2ea2dc7b6019b01d992c3f8fdbae8495134bd25d23fc404a4234080fdd25083b0d160771fff1c619ff342ef4b66380be8b6dd5c69bb2a8230362812b3187ad6a49fc82724bea9f03dbcf9acd3f6ba62367f06da6bb0b2e23b8de06788c592674fef038e4480d94b464f5d0531e5a23f6a8fc45f9dd3436143dd4a2dce210259e8b5ef00a9f76a2465bc180f81345af32a7cf09e93a4f28b3bf019817b65375baecc71a5cc08b47cafe630ed314d238f84a4a946ed5894403925d984a4a26b7115b977787910a77fe4366ff6bd6a0086ef0da024865e644b83361b4338c822d4358efa8ac642e15010a1d92978eeff3c427efa9cbb7c4efcd90cadd83e9648df9ff50d2b42c99aa5f55c1f172b36cce5caedafa0a39a717b9b527b2ebbb1c37c45cf70228d0d8de99d24c4b102f3b30d664bee16439b7dc995707da4497c1f2f4a86c7e112e17d3b4aca60db254f25fd84c88880d003ec8590d4981f95e1621d79af012a7e4f895f5107f13de7a6f5a3ffa8ec2afd232777f572d578dfc24903f100f479228a836bb735cd80fcbd91953fad42dd308b1fe530bdf7edf0567775a69e52b3a609014d5e053877d0336166ee43b2120771f5ade416e8e8a695d81176b22e17a811f40c8d2fb821ff0d87a99a7e67d42e2aa59e49ca05de5663e8245c8d58436a6ef9c2eb6e2ba3506cc2e5489b058444c6de1cf1828dd657efc379efbd540e6a90db836e60b9cb4e64afb12b216cf3a927335a4073410efe16b071b413bb67e0682f14b00be36f3e27df2739d7f14934c15bbfa1cce4be068bf1a4c322221a6cd6b0db5e63df0853262884e3f84a561778a2bb9872d9cc1ec42c8fe4a7131b20f4140daa27165bcac35bebeefcd9617dd6b728b92e2a4b51202c67c5104e03209baa0b183d2800762b22a2cb0867c9898c80fe00f3ce44f62e52b5023497e2ef3139343866ae477ff27c0b25fbbf14ba0c0300a78e724161f6cca4a51c69eed55ce66a38195a835dd653ee89d411b1c6ebb681e1e7465bf8362ca4e57c48c562503f6b2df3754b01b9eb17df555da48fbab5df13f918f31af293d3c2c40080b8d85c385c0a3d409177cc8c6106cbc43313f10944985a1dde62ba0b6f988ff71492fcc2e86cba04b94c9171593e32a16a047ed6e7711772ec80796431dc62e21440a910546ab753ce6d6d80774ca2907fff1068e5e1277a05d6dea1e9fb9cbe7dca9db854890d65ed4e9a9ddcbc8dca4bf66a3646115c666a1c0317762312f941a282432e118fca8ab30cf5516f43488fa4f92481aeb528f039aef2b62ad19e4945cd8c241d3bbf503fae80b7bc6d9e7280d74eafbc9e8b5ecb64549509ec41a27966cad6ce475efb9cf2931e7adda8018a9e54f3492985574ec4863ad28af9e07d8aa525ae0488be5b96bcf0d34aa3191739e5a47e4ab5a41d2b103958b8c6e830e25210aa4b15ecfee7c29b970eb1e5ce665180c0e4fa6d9028f44c2c4cfb751aacd18eb5233c9a67d04821a6c77a29b95d62e0f63fdc0f7dcfc429358c46429f14873447a7b584f91ef73543c8b2c8970f050ba1cc55ff29b8a521a7610e7b0a6a1db748613f33cfa90f200aefc39939b2cab1ea3395ed1555bcfec3602fbc2703a928ee2ebf5236772005d91ea0e2da85e9455d8614b24cc5b23c8461550f5740e6cb5ae67e3b860d8d30f447ee5156cf4f5500e1133ee1db8242da829c80e3b5f42ca96367abf72a9bb612cf8aea109f16a795686b8f1c5557406ea7ddff0ae5700f2f2dbbd59204dffdb6bec965a3f249c4b71e2ea0c4c564f9be6826a8a8a2aa14f81ddf33ecb512cc9e802b155b101a2ce9eb3109b791d0a59743992fbacfc29444021dae4936ecb469cf93cf0d5e890cda995e6892eca567e023fc6d1ddfff54fdb24efed263a888fab7fc6e1a2d85b4e153c68f50e09a69ef447441a415547e7df30b18b01874708e493e1a2aaf491222ae638f296f89d528441ab79917aa224cb0271837f29857c1bbdbf8629fb7e63133bfd82be928fbd3b99a0db4356ffe6339d280a1ccb657832084779f05c54a70c612e9a675e560c3a625120925b02d6439949020885c7b18c4f4993eae082fd66baf1ba64f4f1535bae3b569c87a047e554e029cf0b4e75c5df599d09de8297f9e451325e75ecdd8b0195eb768a3f821d4c617be5c5bdd9e6fe8a5d976ef4f27017523577da213a793aeb363717fde4d4b0368f1b750552c69295e80699b7f1af183602fabbffd2e768b0d5b7629f0094f01a1f5919292efb89cf1fbf48ffdc22fa2c21793ac4d4944270033e3139e9671e70ad83e6d3a0ab44b05d2e8809b6b27773e3bac0339a25cbc6a4afa01e85cb20e0251d4069aad36aa229f99218622ef0d24625fb03200d719dfe44011fd2f2571d46ed24c86eb0cef86893ea0a6425066044d06c10eee1112c4a2e914dba5fc5e91b3c89c631f9d4d7e97081f0150e09d1d91df72c5f52aaa4cd4226c2fc07dbc4bf671af6e1a32d881db5a237893c901230b3bb92bcb2a6a27b51d8d0ae33b16cb513b1d039006ec3bc19f7f2bd7b701d8ff8643accb2036ebedd2eb47f74b851968af99702b428883d5fea510e03f479d2b544e7cbcba1c312ab2d1b1388b4f66053cbbb9b73d4d194fcce2afd36b0960e3038b03ee97568131ceaeb8a3c52851fc30e45bc7fb18bb69109af2aab6cf4ffb714044a52d386c3a8a10687a76de39d41f85555d79db398594267cfef2912c28f993988494aed15fb19b8f04fb2c557d76335d954df22ff6b7fa0d61d25897d545aaa566a9f84e0c823c35cb0bfc4bd78f732301bb53b0a8f3568f8191e90e35fceb62b3f0ece6e05810739cc7c239c5eef6c865588dc4d8e2a7e62cdd9e4229c4aa210562129d4c016e988d34f923b1714926a50d589394c440642e6dea6d5008d0e15778229f18be453659592fe73d704eca2c1899866ff561e59f2fe942b786e92e5e79806f20ddbef6bde0d7fba1b0b07a539e06f7c62dc39985b8f300862e67450b9c2ac9203f522bfe0d020094ccffdfc1575558a5ef879171c71626fd6efdcf07e5d84e4f5d0addf23781cd6bb9c4e2a4c454ca8091776558f51ee554d885a2b969ca07124d14ebdb22b81d82f8dbd47fae83a0c934fffab914e6913eaee9901e4ceaad619d621e865149f0b81a7a447c5e88ba2a14d6de2333455588457244baf500d3ec623cfe394855a944a15464a59855a74375b256c88e89884fca0ef4bdcd6272afaae0084e9cdfddd4a6a15e361f0dbfcb2f243c5b75b99ede8468d73524ae6d45f21406f37f1cb05db7f28c646da9942e353d0a88be1f6b70822d8e79d6c0c252767ebaf8c9cc226b9dd4c09b1ae8ed131ffbd3bc9e9a08980e5b0c753ac5a2a783f7f479de3a07104b0d96736e1568689b2e3cff121da05b27cbb20c2c867e2544547cde417242677366bdb4e0049b4ad6ed669fc45c808ab47dea4e1ead3684a2569bcd73dfe6820446caa57622b2c740fbd4a985937cb26ee25490fe9eee68587f3fc5a389ab00d0d848af2fac45dcc480e6ab30575dd970f34b353c8db031fd53ea0ca051bebd948d5e5a190565f02dfb49985646f25d89d7d834db91bd56699e893ce4a5047df1bd3a3b2dc47eba2f52b5c7c15859d5d3de91b484dc0d911c11aa2a0d005894fd0048e2f3235840d047b93ee8dd5bb23d624c91e082d86a928cb4a6cf64aa2f74645c4121f703a0ed433878c2e4fbf60d8e626b6c5e9a1c64c84a2bd96fb06fbe38a6d29cd4d1b3c08e69f3e50d4170046e71ca499d12d4435ae749a698b22f6f9bc4250c6daff3c9bfae16d0a871cb502be6037e906ae4c1cb49610ffbf2ddbf7d1fe6ec6b37a0065c81ee45e9431d2664d130b9842566a92d08c3fb45eb4800089cd9c900e8b8427547534661e12bd13413b0e29101d8caee10466f4127939cde673d1b2057318766cece6f61c024360ad73a1c03fa615f6100be27712f125cdb1b137a987c0d6bef63f69d91437a5956b0ae0b304dd0d5e6ab544928553707a9725ea5c0e6f52f0c7586b2c478ef91f62c4035e39937220eab11aaa6328dd366c195aae8b9a5f510679e55de1c900f3bc4fdffa1cceb13075b32694ebf78a7835fb5fe410d2f0481738d01667e4cd91e718c6d35cb973884274cd04b3bea4535dfa4dd3a994e9481e16e7ae442ebf462156c004c2761d0abc0765be72ae4bcc535f737e99cf536a2a948bb1b8622dcb7797253fe69d66730dcb9fd61a79792adcd91eabbb529428d75a11a0d881b0b1c4f467fff2b1d4fd9078bf84cd15fbe996c8a5cc1658cd6dfbbfdfc20749edb79a49dd345b323847d50630fb0a02c4239a8bc4017d41b23406545c64524e3ad7b2675151b03106b55b19f9450545cc213414d58fd9c018a1e81f0ee00b60b8f3dffa7c6e87598471e74a179271f4a6fd9265057084e1b1bc8e270582339531c730034f4eafed0960651087d62a053fc402d7fb550a2bba7b8a428a3dd9dbe403465b2cd49b5b0dac3cd534102dfd4622dc47e61cc05c23fb168d0f42143067f97fe082d1844be43d70b8527dae4a7e95064a712e2969e99fbf85de473c69d37931e3dadea9f4b041333c0014f85a24121463e5541e4d16149f36b8d10efcef273f099547c69011f0fb96f2b9c544a988ac1592e84a8e1c16357d59524b1074203d9d02729c3126efa3ad739bab16eda5ad85435b9329f41ba4d0fd6d56e7bec24bf70f1e0384db4976de04ba7ee3b335de9640f340dadc95f03f804c2b950586d170162fc3fe202ce26b3e187debd7beb3d2ff60037d25faf523df12528fbdd0876e66d16614b9be99ca5a19216865b5c914969120a92c364ad7e2196c6224b65ce21d11efb9a57244d8c38cf35439f80d7eb6ea1e73cf7bfc4be2860ba4a42a74a6064d2b5363e121546ef56c2e7745481b2dd581e5e9fdff44ff140d2979a4af7b274de719b44391ad0be316bf5106b0345418f6e4d7ae4b93bee57d7c63ea4a1d8af8d6864c2170b24070f90b29bd85aa059ef69428a9b4c47172539e8cf5e6af2267dc23e8261f3595fffa95bfd53b0a0d0332d08291f4ced3275b585dcf25b88d89ea931c16fa90a2467b5138eb3c831284398f7933d35700991c4e4c83eb797b74ebe4c857b1c1543391a33ae94845a7ad6ef5073b0e91b6529826f2c104bafe9bd6e79469b50f27c9b995f92eaa477eb4a102e7b312681c78dea7c5f7a7311937f940bd941502389c221107eaaeb744d90fd798c38af72f0e0187e637b741307c366985c7e8baf35b85c45d4353d15301a280854b7f293db47a9eb8cba2a2b34f3a523c2ab60bed324c4d56f31fbc9e6f316ac12a41e96c11caca953a79c26d0d65df24d5bf86d6b5631f324bc5204648d0766a6046b798582dd36d26e8c672bca0f549ea54f0a2537e0e6ab0ab3615b667dc7e261b1b26ae781a5b2defb4fccad09a9fbd997f90b2e52c4efd33e3c93cd60cc4ada31b9236baea74713cac275eb6e0a561592365a1d60a62fa460e6e642919875e2449abaf7ed769a52126fafc09f7185551baa3d2262959b57753999b2ececb513ef9d30412b0c767f3d7621dbdfc3604291b779228d89748d35eab7bdd32c261fbcd8d3dc7661d152951ac28bebcb29daf3a0bdedd202431527d10948e9c2efb302361e74f3d1b9b008f24f01ea9f2233e034400585521aca123c270f1c387b64944344ae9a1245f5d7b3a30df63d4603581eabed60ae66ee73d2f5b57ba482ce0adade2f6584160023501163188b4a82fbf5ae6904e2578de763af71f3a9962a774cd313b960f4bc1eab463b2555877fdf2d6013f241540cb66c0166648897ab1a3439c3fc032cc526e36bc956bd0b4bec9893581d7e54058555db01884a6ba09a1ae0132768cbedcb2878ded18a77e6b00d7e3e4289977e1dba1515aed8e448baa37416552d133edf7c4986f3bb11a5394dc6641784065e4969b3c67d38598ccaf4e3d495c2392aa6112543746a2b0fab9b12d19ff913fe710be77805f8e4087066cd6cde7e56cad940a7d5b29eac15f54bbe5b82b4fec5d6a6a7a6123c1a85942afe5fc6684ec7cb2ed915490df351a3a08b54310e5fab99c8648bb63dcd8e3cb6cbfc5bbd58f084d7a81ef27dd5acb59445849764446cae5c5c514d05eeb1fa9ac260ebe5603a45f3d092eaa2103849072eff6823478e513f2f81e568088fd72abe6517dac206f6ac47b40112b052ec22347c179719a541235a2483362f44ef7624d677f91754d7f4c8d22c2f3499c0335f7062fd01260c0168e2290595947c6baf77250dc74ee4db81a4c1825c407bfa7b09c76b5789fbb7bbb02acb70e6d60a5c38112000c84075656ea54925c773d03e71abee3159a0c094bf2da35d9443f9cd733b74d542333c8018df849b6884ea65746e65a4bcbceaf39c5225e5df2f2a571d9eeceb7251559fd981b896f3bb8a98fb44099116f72ea3171e6fd26057d028a0dcc73c7cb2e1126a80773a79b77c00cc9f9b2702c047df1ce1da62c1529d0138c859367f733182f91b5cfd128f3fe8a167064f6963fd04eb3ccce4d5478ae60a24df3258f9c0e9c9374fa1a5ee73e8a10029a2f2a6485377558e582b15b24624684f8594110c037173e00fa1cc7e3b5f5c0bfb605ea51cea52b2414163cda4f5c0ddf1f2fdd43e45cf165e85e9809ba14d6f4f7c08f49f48aef9df747b5e6dabd7da928d12640108d17dd5bad9a3ec9df1d007bdac5e0be03d7f85d65a1e35802f8ab8daec06cb8999b2925347888edb303237ee8f7a8fed6b9310b311c71ffb7c72f728ec9ff0d8d26f23c4adabd20e55d92cc74fd172f201aadad6aa0671f2d9e465a3c6d881c9b7fc78d22b22af34d5a0d86b7d935b2df1edadb166a89d81bab701a8d40cedae45b30dfacae80954240bfa347c9a5cf62f6b216bc8640b4a073f4d64394011e91e5bdf1b7eb2e526ff44ae254881501425047343abf2c48f2b52164b64546915df872fd3d45d9253582b4f1e121db3fcd3a6145c0a45e732caf9acbfddf413474c5ead0892525c1383bd42df466f095ad63c5bbb8564036282cccb72bf41070e52d493643ccc1c6f2785b63e16da90f07b84633dea4b07c7298ea149be69acc279097bc0d75c0a549c5c9d0444801e4fdd19f3f7690459550686a881f548532469f4e1720e9e36104eb157884751d8fda3757ce23964f7431c6a3899e3e06466843eec94fa9daa162763191016048e2cecc8d545a38c3b0b2f80f42f812eef01fe6749d8ba059681ebef973d452c580825cf2a2a6fbe9a420858e0ab1c9bd15277e2a543a3b0e120f9a09942333550afb02b6268ae7c21a8f4c68908536e94675a1334f7966daf20d900b7f0c66528038052c5fadc509a5a22d9bea92ab93fde63417b16fade888f0e11395a279bb7c43343ea8448e875824a452628f0c558ec3ccb220705c697a6dd1d09fe470db7c3c510b53170275609394abfebbdd0e0e7b892f4ff0c0a5a3e2b889ee417d7d4c35f9a541869865e89cf5d9993e6e4d4657dac583c5251053915804dc41cb9b03215797a04495828cb9a25f11467be129d7ea34cac7de5f4b0a91f978c425a3bc8f5d85c74d599ad57bfdf5b4eb7190b09c2dbe96c662f19b9c3052acb3548ba674a6995bfd2561382b3a81de5d810db23684357dad19d44c06f3d0f252e761ceb89c142f14181283ced033631181defffb7e11e54f6a059f708439e0f80d18035477896196b068fe9aa4e3c2f31e9d11b2771f9d137605481d0d997d0e205e2933b2d4ff5593a1f1cc172a5a26694dd23edde908377d8be386d9ae5875ef4fff4979d23090964f73016d089535d3149fecd23d521e6ae556cd0f4ff4e8e4e7b37c0d628e2a94804aaa6c5be5b732ebc711d988d0a0a54277269aee6a6145ca2ab773a54c97355b8bfa6beaca40ede76a81ece2a5fc83283ec7188aab1e13b56f851ccb6c3d6b9d6601141603769c7f7e42d49aad4d83ce531115fefdc604b38c0bab87f7958a89ea958a766999ebeee39c1180704ff7e050bc1f4c6c5b6083014d1e2dea0f447212a95a25ce7a83ab0bab07610fc2485b6c311f26c5492fb826b069ff6e6734ee2a0412fe3be289c69c7ea6c19e60fee36a16ad497f1ee787c1f7461251ff765b14038c7eebfd665c111ad2343fbbbdbf327ddaa63e33613724f1f2e8574cf036abaea26d46751b382d0b44d314c48463bb5a37c72ee7d84187b7832aa510d3309b2f2960b07712663d4d33c3ae388a39c5922dc75f7cd9d7052623d0b841083580ee4f8ff576831a05c2b473923350f2355e88f06ab438f05b3c6aa5ee843c92968a33f44caf2eb3584caecc99cc2b9a8cd065573496b681e69f081056c3c905f95f616a4d2ea21ee63e68b972dfbb30b41cb00b6a0918764609fe0c7e9043b3ca57616ef002bb0c64acdd74b95348e19fadd62e773dcf4aa55b25a55343a33fb2257df1cc073e88566538f10368b8093b22cb273417e279c0ef5b86cdd95aa3215f884dfb5dee05966895e555a4995200bc132f3233b29c15343c3d4ac537ef6c174e06e210811da6bb5808a194749192b67c4446abe1863ff8a8fcea799d3e6903fd27fb7bf014895fe38715cc43142fd1161b5d2104cb2d9ea91da55ef78e729873d4e8e9e290106795b4f586ff3ab9a983347a09acc66d45c9c548ca98121c5c9c70cc63b08d9e441c0c92e6f46b496e78eeeb07aedc78777623d3b4bbcc4638552ac32309a98e0bcb05f8a74c355dd616e79af6448d6e14064440ff608aace56e2c3ee8938b3e2d9a2b659b953611a062a0d2f9a030e6e82a646422a6ac8a0dff8fa865ca9944795b2a537cf238f7dfa468810562bcd29a522ca05c206bf1c6abfe9473cf76c181227d4605b3f386d18e97c8e4e40f61620d9cf8c477589a89c7fdba86f95be084dea862baf446707d579e7e71a575f2a62c5ecc7650077588a51e8d2a43de25692e94aa4e064d0cbf03931772602f158e7bbd7cc692981e439a89d9f0ddc0deed9f7adda359e6f8b1f727505d0a1e9159da10425a2fda146476b133f364d9ea64467d09b4355fe83e54fc05c3545fc652d37f7d337de6a04791f4150795e9fb16e5ab029ce7ef0310e2f07877e9a0936528fe2dc0851e30c6d4ab4f615430c7748ac0646bbad76d89b79f7ea51b0849dcd23c6385b0df794abf120f49bda0d882109dd4cc8406d3c623dfa389bb66c55871415b335b68cd1ebdc4d6c669c8716553b3061d7ad1ce4734759c97ad3785af68f5d2112c5fd655b0802cc194859fef9f590435adcbd9f1f23e9e053bbdb3611e1475767dc1c220c79725003dd4d3976353dfb68845f32033d1469a097506fd0d8c6e959c7a7b6016581bba16e1a9b98ce1793b2163af3afa38cb1b66c1de2a83e90efc6b812c7c452eb3fd5ee0e5505b144702e72d3a42f07bcb0f1890efe4044b3f2f316e8266a903fd2cb5d7a752c951f46dcc759c4def7e215b82a465d89cea55e3963838e8bde03c84a9323896bb6dc6c10cba3bfb100f5f633ee031317d189452f5ee2a6da367d01247fff2d0b371beadd111b6725d44687400e55766dbd0cc6d587abf1d86f38a389cc72f538855bb0e88fbf38ec02f50e897ebcb7f5064d0c72caf4bb3a956eb206cb3a67916786aec69281e08f866bcd67ae577a13ed31ab4e05f84669802467043d623f5c5a08850a6b3f387ff17776a540d64213183a5c6eebd01d23f84fb456191485b3c23462712c4e8645c187e031ae4f53dc9a802cad9ff42d77678a181cc0cadb30eb6951dbf6433cf1766aeb35b809f4c279012a5170b75f727a69fb2079da0630cd506b8ae2bad6ffec8bed24d1232fc95c1d914bf305ce0ef39a13802a9b40c445cd83f5c598f740454fb25838c2244aada436a804720956d95639fdd9a5c45a0da7518bf77c5ade9b96f0b57c7eafc8d09fb1a913bc103b1bd2d3c46c2e6ef9e8eb0ac7bf579af2d4ddaf2c197fec4d856f3ac19b47764bf931081505dd0839c2ddb5f3e58126b74b58a92870b05c4b241e5ef19b11a16ca7e5e5ff9c759b7eadb7fd9e0c73ab4539927b1fe62a205b7f9e80cbe5825fe38ef7a215da8def88a53ce956f5285f62f2645b04cc60b36c861d97cd96ae824ef76278f04edace083df07450ba76ec5e3bc139499b4ee6f425ae49616b565fe5e3402087cd056bb16ce8c8687a2a2d6fd67ca59e6a71989183e7e44a9d54b5c9986534ceb8fbd14359c5407254f64f65a1243f68a8697cb58c89d907b7094f55a66f8343a354980afba84e34b7a5473c3fe1c2204de84e31b120478f5282f1d348f6dc967f653229c13e51113b08ccd4a0e789e43d57ec8d48ab3d7bad2ab4beaed1f4606107c69e2874bba691377d5173ede253be6c57b2a40200f81a08f8deffd46db98515e53693aadc110725d2351835695508ebf2da93bf3e8722a94765ee2325923cd7ea987f93c7d9851b28c5d7f113d31e6dc24cd7dddcd343e075fc97395f36bae6a16f5eb3c2300528119d3e3b97ddb6da9f5cc77e160aa2e0b22a8d3cef25dc105a46a0d1b5fca286e7d3f4d5fd0aace82299881cedba4cc1346254d24bd5432c04159902ee82956e6eb0e4452de54077d6e1ddd3f90d4dde4fd3dc0b3315e6430abc84852eb98b0dc6b89c8595f0504d9875042df9e251fccb73893627142e7191eb2f27ed1584bc5ed18b99c98dd41dd3fd39c8dc38749389435895d98edb0d0ba79f1efbfc27379c3328e474c05b4ca2d3a401d958b466521db6060558358fde18151d408051bd787783adbb874212136c155aa67664ce8494b3e7f07ea60a77d30a8863e839504fa9e0ce35ad01983e935623141eee1b820aa73768c5fd5945dc281da1ecca13e1ab064087e72c2e66bc98d1e363109368ffa078f8894e3a31a3666afad9cff9bcc7b0137ea7ac84e220e862439217711eee3a856f0e2998a23771986a164ea9e27dc387bade8c8e80ba762fb2f80bd9ebf8ca4fce7ef6f406a30e3faf21b5e33eeecd59c83ceee118d666856ba3af8ace0556501ca427e0ee4057d656b30ec88d5421c3db9a0f3e20d5ed49925226f4240d5f8cc86ec9c466ef42aea7247ccdae4996296045a4e3fe6eb462a635d9ed77d9044df2c651eb20c4667d9a7c93d373c8404ab49368cb64480356ab6a85f0b9ee8c7cec2f31016dfeee61632920ca4b96326bf4e29469d5b437f0b546d6655eab052141ad3443cc5a16826efb8a81f802d4ca956fdb7b40d491c339660dd1ae8e15f9c6e6b38d8aeb62f9f302735030247711f14df891c568990dfda7bdff4572f661c1e4264d9187ef144a7a97f0d8d72273152027d63340e7ebb2825b8105d9f6fe234b1661aead3d6759ecef57e6176367535456d5a720f30052275fddba80b209fefd3712bbfd0d693d29acd88d11d98585e659a52fc001b1a125ec7e934462a92b7bcd3f6b4249b875aafc087f361a659701c839d4086edaad79c8635d77541ea4522146ce99936f41cc7ba09e37b3abc70a4ddeb133f95e7694181c427d8499ba1c429599fb30183fde4c1d36cf17852b3ccfd349d77b9bb2d5ea7dc68da35f65741a6ff48ef6ed9502fad2f542c4588f29067048487281161ea63d883839f3df28c2e987f289aaa461785580ad4dc1287985a44df2453f9c725fe3bab1723d5f58b0baa715ff183b4cb23824880eab82a3fc45c0c46d94108f3f82b88bfb290def312aa9945784ab523edb9b70eca2c1074b59cb4cb604e1d9dcaaf22e742a14429e788170abcdd445904f1ea3eb6319835ed910a0381f3c240d8b050fffe019122ee9dfcee138832dada2fdbf016be063572581922706d3d08a8984fb9f042d113c48ee6d45b5e038fd67e51e09d70b63a81bcf041ef13b55ba51d3aaf22662d46f2d6b1028b1f5dfbceb29bdad9bbf9267b489bd5b2047ce24cf7ad58cfd94ded02de70e394da6a0527d55d92f855483502e96da0cd9575ce659d07faa07076b3bad49b17a540882b88e8a256d0e942b162af64320f12f9cb018566f822da27c42ab8b76c46c4d66047c2382da449d8b1b6184cb5993e4f96006042f18cafb4fb677a73a7cd621d9924365171a3093229e22b28e884887803a42db53b6aa9a6dee08d218ace52a9e195845a3bfc53174f647541671f8666dc234266029a67adbd043ffd258edfd453c9da9e6731afc582df63bf5ad69171d60a7c89351faef4f7225ee801c6bd16daacaa0d16cceb8c489c01de90436821cdd2617b7cc5d483d3c4ce3436df75b3eca87a4b64c3cecc692b381562f7b3513ebf1d72a6d3c7b6c9b70c31c5568d55063d32bdcb2eb7e241a6c29f88ec5ec1926dc5d287f04e57fe70ba53b64da5750ee52ed8a3d2d4e02643f6c857919322690d1573172a6a4e7f62fa41e518d74fc200479315fe619a529834faa34c3bbde3ccdb0e4d9821da2bdc18ccdc19baf7c754e5a659cb4a07b5bd248853941b3072bd45146f3efc2831078f87f1d0804347bfa01056b27a87ea03b4e283ea37b128f82d61c3cd5695e41ff2265dd2e86ed7c3c86c49456a63a8bd774cb4f043e75825162a7417d5831b4d5dc2ab87fe9b2a887851334a01f33238a73a8a4d6dc2576dcebff61d1b69a4973a5119d29f0dfdbfd4bc323dd313c1711a630fbe8a90de6e21b13ad7e400fe508e6f377090c6271610f48c25ad883a4c98779d91ee77643734b8fbc82960345f93507f48d4475a8394f14fd530755351f3403d0f528358b23429d7c4bf76a1b887268334c305105f570b7619fd7aa2a55419cf1ddfa5be9bc838437b5cc481e56424054923c5e71c604934a7cb4c6726947f344d4389f0638d12fc56d291f184666c377db37e2b6d60acb4170dc178bd0edaf7c66f56be37e5465e385a1fc2b1546b5dfc176956c301b903239b3de48e702aa997cdb8ecab6b2fad665c33c5074a89d0b724881faa55fe1f3559f526e4c4c1e32dbb9c03fdac7e4eb59d99d850f057b0bc113945c4bbf26f4da3db3e0bf7a8ec9d9b974670290dea9b7dd8c5e58c3a5a51f8f699ef3735f4ea5a51a94f92b23775fcb332f91e9744c2a162236dbee25633dab35420e26862478e814fa382865a98d4aa369e14f814e9804d807b03b70bafe6be214384195f3809ef523d2f287eb40002adf966431b431fdc1b6d0ca271117ee0f52de3e5c382d1186b366ed22017c8b82a7cbac9956ef42d2a96890254cc5ee2fa7e272252bf748eda4ae9290128cf4079cb84d6ccdbfbb7f404b187a05e6702abe78c98530bc0bb23bdbf0c5ceda47417646256c838e3ea9e78606355195a3bd5317f6125b0b29d1e799135c2c44b183adfb02825f1780600d7e49db941ef8caf5f3f43dd68759d5b5f7bda9ba7313712c02c8d330c491176c02bb680888f1112e71be97c80e29f73d84e3dbdfeedef03cad013ec7fdde0e8014f6c24f061bfedc838af6c4d10900494eebb47698552662f0874fd44687ab4b1a6c914e820c8c56594f1c8a0ef71c2760ddaf7027ab48cbb067b651d63db21fb83b2b15ba9fd2e7ae31ae450728475ee9ebeb5a4932d8533c00f6c74d3c231084f7620eb5a6c8cf990e3080f21d49482dcafab162d2253867fee0917e8e523c47c8a472bed2b9980a4537f03680c738cf298a969e9ad93ce51c9f1e8d498d10248f2ceb512a25ae1d57f4cd0f4542469277c50bd222a3bee3f74ff7016465fd44be06cd83be1d742cdb4169c2f354b8c4837cea696e800c6b9c0e362d2daa2c01892849c98ed0ac45ba8a55e06796c2d253e0163bebaebba9914346adc0dd0de99a548f0e706a8c1c96e194636277302b3310796247ea849242f5ee32e9fe78b8f7023bc27fe7d9275e58d6c93f1c723009c9086991e1da19af8b47f8d183778f1c130ddce756d97ceee0cef04d7441dc34a3126a03d5ee6dee11b11e87b6035c454fedd3e0b7514b4c66e8e6393feb3effb20f9f336ac82719d719c771636bd5d6fa69ef397938f151f7f14811c8cbea94be0cb1d6099f3de5d8336080d30f3cdd7e2fafcd589e64f6ec313aa75cc4fe52f81153871481bc9c4c754314f95d30ad9b7cf4c8ad74afce4a56e040a7d25647277b9cee4b652cb6c475f8e9feef414222b08662f797a68e7b056abd622f8d25229d138f4ea506a3bd7da475654cdccead8c5247225dd0c663370d21d5f3060ec4be29530dcce2bc497797070f9bf498119c83e0a92f88171e937b1cd30bafc7255f939b546a7eabdf263ea7047de81034c6c14cbd7cd3680348d959d2a2b35a463a96afec31d872ba52bb9621b854d26514a786d0bd5e8e44f99a58e4350880232fe45c291b9d6bca6a305eed6cf6cc3b6339c4c57b90a94802cfcf75053caaf1e9c46d9e44c1f5304c30a85746d2c31bdb329b7bc90aa5949a42d929abea2a0ed84e81bd9b8e2e5b2d604dc26b111348c5990eb8f9846c07b024dfbea3560a6dbb42dccb77b106ebfb3d8b818c29a8aed27239aa895a46a6bc01b8d92e850749cd66a714330bded38496c642846d135c6a42984354dedbc3aa8e779ed35cd5839f08bdf7480846721adc75a582c4e0bc2e5d089e1389408d5a2a4adcfda1de5d16e6aadbabbf55fdebaec124ddfe72d8b26b06182aa973d6328b2d3dfa0d0939158c81e19ad3c69d87f2a457df127c750411bb820410a11c02b553e81db6adc2638e7aefe6070bbf7280778f71ec26554eab6d4220a30596380dd863f60f9f82323a6d9d47e249b18de1fddc6aee15502e218aeda099d4557ae80ece27a69e2c245772d026b8874192695e46448c246c7872c95dad1626d815bf2c8bb011e7cba70959ffa4daf0d55c72557183342dcb5ce3cc8374c82020f6c63e11495834de96b8c3e8dc75fa9f48bfac308a25bad7c42e762c65bbb345797678a60ec6f85f0e801bb78e9232fbef8e0d1f98dcc7e080d0eb6a10975ac4774e8df825daa54338ba5c42e03aa53fc2461073d3fffb2d026dd97eccf60940e0fae7a59b63693f8651ec112d5935ee5c08462e7c85ee7444b5c4436e4d8ad1c999d4d6953b40990aafe7da8b9e1dad7b7fed7cf5448cd238e18dfc68d8eb8fef80195795a08addfbb202f6525bd6e12cc9e5ae81460e837ce1da71e2c38260d739b0f2d1cd1be92cc383eef6b27c9744fed18c11a2df723289f943fc0ef3eec5d71dfaaa90e3911d44f656b6170a40a27ae213cb1f92acaeb5d75f6a0eaa36cf183853935e2076d41b53ff3b286f6f0e2543ca70d34d3424f38b3889c812792c1b7114bd17e6bcb2be64c6cd110b158a5f115fb2510a137fe7db6bbb414773448ee3e9b7e4f63e3a2c64c6e1d2d5eb457cedce9e59a36985215dc2ca45c848f804284d64d07093358a85554732eb725c4cf45134120cdc66f000e3c8edde6ce5dc2164c38ef4e987a755688372e2577fa5ad8cc105cd201a830d4417265c698e0f79b0198b9fca651397a3f148f64bb8a94f6a957a65e13b3ed624404160497e413dd59b4297a6fcb23463b0a40fb25ca820ee7e29cba23c746b6ac392bf03944854d195a985d660ffabc09031cf1b5bc0f772b242b77c9abf588f862113d492cc4dad339f39587518c38f87fe43ad28c6dd94d63e2b0b819e28403ebe2a9bdecefbc9a38d1c11fe279de216c57f7659df2790aa4abf0a7265ad56487bf5705c537703fed63fd0ecdcad25af3f34e10cd793710b837626c95b8478ebf096f5f740f8d9a3c4904d510367947c6f91de8f48a573cdfc747446a984612c582df7e37b6c5a4582b1bbecfd2f2af9583430a1ec2dcf5052803cbcb8cda8c907bdf827e1ae8544d9e58a466c2aac9567a941a11957be72a13e8f2763b35f174cc0061a238ab08b02f6124471530d3c7f8af711e9019cf79bd5883933b13682034e153227b668e9fb69692f76a1ce6d59c0f29e6a8e0de6da2e86041bd338cfe72352b2b0d58cee102d9156ef9c5918ff3177f6f3a4de8db0af0c0dfc449915a5ff6bf01e63e4067b7638dadd369f0460e402bb3b919dc70d2523f9c590eeb5d6245c9c98c422fa5c5ad3d882949b879d0b8acd9740d8072f4d810b72c78ae3be19e1c0e9303742c20a5bb1f721100c7dff24eca2640e7bb3948193424f622331a73d4e9226d6dffe89983e592fe2a79ac04438e37d3cdffd62aa5471c6af21eabd804ed50e5c4d789fe699b8d414f5af984865470e8f49560cc468d0ac68b59ed2f202b53b7ecc0265281a0b9686365751763e9e165bd5974cb020e8a096981fe8f12e9e08da85bf75cf581882be48f296b88b18826c43f51caecfe36edc874eb3d796f70229c74b7b9b307bfe8f2a3da7979d260352aa45432661e01fbff8590a332c1249d5c2484db1c36d569e986fef62000f056c96265240215eadbd6bb43b4d078dc3e6835ad3a9be043a2dc66606fa2e5f8c6a719219c3428019b95797043219d1cf50c48e921631ab40a387a05ca1201391d459fe3952991cd6ce7e4fcc90983fdbfc2eef455387d5fa3f0464b769da27849de577fd9081c5e5dde477a0ab535893bb77ca8736b0b05bb474b84fa32b8e57c2730df0172d7b38cd9a870277873861cb0142f7f443bb4ed0d0a20cd6c05c9252d1dc4f12e923c1e5c037c171c2fa87c285db5793ad2a336039357dc1891fd935fc151ac3cb5fdeb5136573a3bdb0d9c7d2fe559b35a56b2ab1f114afd75190e1756bd6e0a79fe640ef78f3a78553128349a85fb07c84ba93baba42742a5cb51b2a86c7061aab6dcfc9b879b988341cb05541730a0b30a27f71d9bfa13509b95fb128dadd08ec707da07ef8658693e4464acade2be0be1019513248c5b48048e7d54196e94cade8e7338622ac390e69f6bd1463a6aef22593071bffdce62843662e0a6a04067d03f6de7ee43b8145492e9d1a433bcf282cf2529f9b344137dd0da9121412c63a2fb302c86a944caf78bda8e628d7fe10e53f0f85cf91daee1fe52f2183c758fdadc1ec2b8971b6b10404c5cdefa42e44a4fbcfb65143bcd4d650928f174f326dbb22150d3ff5b2b65da3e6787066fd80cb5be5e64b173219cb50926d34603fe137faf553167200cfc59a3a485122a560d3ba2ac3b038b5b3f853a42e8891e3af4b01ece8dbfdf4d22749eb0c4887ffd6c87a8a7673cfa284c1df609772def72fc14f8d90d7480a52a0df908a588db94b558f5052ba78898c7cbb7fa71432d4ee32b225e249ee16add8f11cffd96f91833bdc98767eb456185cecf08ae8d86ea9b08b8b1b1fbc645500d13a0d07b449c37d057b56b342b748fa51936197219d1c8534a0a15b5fc9a54eaa50b5d1f3b97f09830857a7c296f8fd617e9cad781fc762303975a691f93a9da4dc2c9b9de3ce4e7beb9afa73419555e894ff1744e048df9b7d52834c3e3578a4da2f093b780b8f591e8907f8b86b9992978788acff241cdb8f71e27f6ddfcec39558f6dac7cad4429438689059ac1b1901968f1894b4d731d8fc194034dd5594ae6e42fd2eaab847ae6742516a5622eaa3c0ef19242e2daf1354a02793415cbe1cb1a5d5fd0efcb0e3b0dcd812e1545e4fe21fbe2070b095580ea2fd85e4d95129c9d6a21804f9bc0b4a3e3d1099bafcdb0d2daf73c6ce836a2d814ca4729eb165df280fdf8e782e459d63e7c1ba83c2170cb97819514e1e36e3660cd3d8ffe46dbe7f63ca99a73ee4f070f62da0ac25574d75dc21ad053bbfd3af1b02fcbd9ae97a9935478fc114cd4478dbe24c926def4592391fd22b950b6824022d738748fe8eef4f8f0f77867e6f47bdea0cbe7b9a25df007cc2c3b32764bd2ec54933254155bbb5ffbf5e39be8231d4a5ae15afc8f07bc85b76b2801ccc58a3dbc04d45db481aba6841dfcbac404ac6b8f651c00a0f44f67ee3b490444e6074a687aec3219931df955e5df3038b80cd081bbc93a2b13708fba9ff7ebf43259d1e0e4d19171b516cd3ed51b375890e9bf3b43278a04e69135cd11e62701d67546e98e15abdc4e7cf095012adeca96a5798bc93aee354960503b221eadd6a11af2436ca6c6bef549be104d06ff05b2d89f3c8a2c7a065eb0c0ae4353c5bf516b82dd7e45f8cf1d3af99a780dfbdbddd7cbc32519fc52347b0330553810abb211117113acb28a9fd5297f72bfbcc79ec6ec144ce85302ada1e2eab5317bd99d4b5a7ba8cbc03ff8df121023e212546a08a38204fafe8bca90399d590d4746c09b30935f5c6fd662763934d5fcbaa844ecfb291437c186b62cd5d992dd3dc7c8553e013e909f366df24ea08132d049eed0279182328bf52ecffc172f5d9dacea514376b578198b2b86ffd5c430ae3f04842dc61cdf236b1fc30e4b0e908fbeb2b5ddd6f5d33115c3a46003990525ca92dcc7af21d619c68445df78a80e40873de4e2c7dfe9480d9f02986860604818fc1c8da7a8ba47a37c9f830190995ae909fbc9896f905918ef02afccc55948d9ccc8d3ceda90c50e1fec4c2f748502fada5b020c7ccceb8d60bee9c07e8c803a16da0db293d4a3dc4c900204997a67d298cb2a93442d00e58b41b65bc271ee16117756f21b00d445536c5c45b16042db57b75c84263e03c8fa01ef7f179ab108c668f5e906c0b5b872b726045bdc2b0604848fee157158f86c0b08f226adeab357a82d1411cdfaaec8610f057e755eba2620a88d83f3ac1a839b805d1e05b5ac7231e78d3d16a8a5185f9e4dc049e39c11bf77059861eecf0e1cb0d5777d967d1153988db3906205c0b027e48be913bece1e88bae1f6794b7eed096dba6154f3a699fcaab7b6ad04157d0337db7b8cf1d9d776c8885202293f8641fc2fafa6c198ee206b762458974bb36cac01433bbb3b98bfebe07f2a2940ab6b96f6e7d8ae91507f528ec7b1fa25ce36845017516b7b76c9e9830500bdbe57eebc35221ca11cd1306434ead1ff41326674dc7c20995760e1a31a8c15447ab024de4f35235826559eb5e48c85f723fb1ee4535fe1a55952a59ae6dfe7de2d6b43e9a488669a3909c995909e019ef07b343bec4d14346aa1352c99704164e5bbdc0181c0f4e234418076dcda66c7b6e8e05f8cbfcbec42fa81d7c9da2947ba9482cf0fd9c8d133d0cb0db56b9dedb29d5588831c593b41ed0e1a9d116578ba84e0035aac3a3ea5be387669eb90ebfb22dd71a19eb2e7fa73381f3248331eac5491a50b18d2597fdfe7136e0e16436cb65448e5e5b58f4f6094fb184fe27ebae5420961704910cd8b36f26a6be3d0425015b28f2795f89c1ba09f11df1302fd3a9eb75cffd5db1a14448500a74a570b2143b0728cf5a4d33a030bfe1892ba9c6d21b8aa7567094e11cd6912503a73284a5c904a6895327a6dcb67f1d891ccd5a033cc31900af004eb96cbf43f73e998cc439493f982b62a1f306298820fd9edfe4d501bf6df1af3afed4f6342ebbfb19147cbdf8bfa9cb66e70b2d83272088343f702fa1a9cc5f5cf39aca865676709207e8e4c75d85df6cf4e480fde8e725c21586d2669d25cd807e51348bdd07fef3980db185544d366fb983ccd98a30a353ebdbc3620b0871624d157afb9ff60dbf90f2f75011f9e2335d337f9f3be79f49c00608ddcb31eadcbef6d01e7e44daeebbfefaf5cfb55d9bd44b31602d528759420853b3f13b2ea9de971c6625b0be74d6c8e4a746b9fe1615040193bc003a0412d4fc2a4b8c6fab24a074e4ac483d6bd7310d3760343a895d1ac8e274ec274ea9e9bddc562a591393391eb9a4467fba8d410e9b980d303c4820596eb0b0bca06ea4b32a2671bae74daa9569ea76dc924920b53d3bc8223cce6fe442a165423e452a1381c563d646a3ab679905d9dc02c1d283c5bf5caea6c945ea6cd3f8406a3a0ce3295f30d2fabdee37ce5ec59ebc965888046fd638b5eb99814ca39cd575654bf898b118f8a5b169789535b79be6023dc2c280a18895a6dea12eeb18b8ef6efd276dc9114d7e613cf4788e3f603b06b79264f287a269d190dd5d6d389514515292dfad8aab1e587d00dec46250e98be92672df44d269e3667905738f4a802498223c9ce74b8939a64e8e3e35fc396acd07ecff704193e5cedd6ebc3769b86c5a1ebb442da7a29dd8f859f1ba6b8fca12091067c18569df8f248e17c82821d939111535b6e0263041a3bbb3d62488617fdf488004f3562aaf757fafb5aff57eeecbdc65f4f73df34267d0de03bbd310d4a8f61cc39dbee053fc1cf1f189d90f57b64748df28c498bec282c4dcb57e80dde2466b4edf4fbf13444003aa24782af9ff5c520979da067d51313b7664d7b7c245d6accb6aef4562203eee0e164087a206f21de7c57bf561557c439e4d1c1d3283ce21f0c1086471708451e29a256310d9a6110ef643493b80879a92544f0edf78bab6a958020714ea462c66ac48f997e99faadf83a2d59d0486388788056a438dcd26063ddaebef6ee4126b62aa980aae8156ca50007dfa6263a1a570bddea140130a95784a8a0e65b40095f1179af3fabbff97cf3c25a8b9d1433109a71d213f4510315958819cd7721e91b8fefb7b732904f59b160cef698714ddec90b7df806e433c76854583507139ad72344cb246300220b7d9c5f07037b935759ebbf6eddc516ee00cae170993c3e086de1c4677b0e6c16b276bbef25e380c091db19dbb097a2765344173909966cc303d263ff9f5e1e69cccfd5ae4fbffe5fe01ca139ad5352bfde98aae2fedf9f8c61bfab4239eb5a38eb809a707536cfecdca4c75f3d90db2d3a912897877c37b2f3bcaf732f8d9b75e4601cd5ac4587b093490d5fdfd9d1df934bf4294bf075f567c9b3682f90f1493cf998f2d472c6d32b103f27f7137eb4eee29c4adfef703304ddc88b170fd1784acfcdb80ddf0b17d54d5297d1192a5f53c98c16d7a6e9f58141965c720cdb849a6ee20b7958459d95b1e96e440bae477986b93ba09128b51b31615cb1e27ccfc246d3129a9918130b0db592fca66edd55c6a1f51bfc7aab92ec1c2e8dcf933a432fd604de89571fddb0911f930254af6f91f5261495b298440a2425fd83f371bf8fc07356d4710555c1b08b847292c42a860fed6ef169ec6816be51fd7a89b3eea19c68ebaff812956adf738a0813da2a037e752f7c089a4336036284f8777cd8d5e0bb62fe7a6b3fae3bc847f71bd6ab331bdd0b39497b60bb916fb623af427345f579a18709bd02350a5124444f4293b784abb87ff51bc8c5a2ee35b4f7cdd4745895bb4c52bd9c8a2f1c63b063996639a631e1804f9ae069087b28950065d5d6345ae9552d26bac8590a15c59d1fa9ed54b561f3e6896185cee748363d54b5a889580c1cbe3429630ab508e4e92ad205edf547155960bd0b141c4658a1e8acaf524f8702989f74fae071ac4e99b1431be0222484da68c4b47fc28b241a3c6a321ab41b641ce777e834405203b67562c465039f2bce1f7042a2ade8e02f1ba54ac276cb015a5bcb2c4807a153f7af32656aab7417b7b6aa76260604340201d712bfc3bed463d9b72ff51418fea7d58f0353189105539398c256a318978f81b2a0ffc2bbb0cc304cd9f3ec8f64dd8b76ea627ae6d8c2c64b8938a8cc7dec9510d7f3059fac02ae2447fa20c747dfe50609074b1378a806ce3cfa087afa2aea2c81294c5fb2cdfd79f2f141b6ef84a85c1bdab8b21bea80af48cbe16f1dedccd46fa74c8bd156ec71f65eb652a7d6489b492b40c0b435c2baf6b61cf3b6c640453d8d3f30ae26345be63f3e3363817f1a8f6b312e77daefffd90060f9d850e80c3eeaac04bfceeda3461cb29f47f389eeb6ebe8e3659bd2ff23b7e9c874f0b8485c0fbe22b5bcb5a41c3cb32b34b334a298a58e3e67b36c9671815666ca8727a3d9ae7cd9693e194b5bab173a688aafab6be33dc35b10fa6972c935637507a8c3cfcb3bf02d70aada6fb19fdbc6c5a1952e3753fcdfedfc8d6a657607a702c5af3ed40d546a9c8dea60c74da25e7328375ff03cad34799c4b8901f6aabbab7460058e08b0f37c04833adef15596e085a9187829bb13ac96e910bb0e6b3c8a16be827091dfc89723c36b614b01acf2a24559370f576fa7bad5042e05faf13acfbf14ca294df2d81b299fa50ad229ca67cc3cb7329cd5c0de10c56f47c18b0c62a7b0527f5a782886c4af678ea1e5fe46b58b0151242dca023f9ad35d167c691ee106802233a6a5cdccc2c907dd069868036b1b2d1cc89bf00e4c1217f85c3656a1fab01958f39473ef83d216dae606ca0f4da316a494f299213557b52a4335cb5e5e6bbfd51b6074d8c2634cadafbb0196d4dd1cbe3a11fcea8fff112817cc192c5fd95c558aef6dcd4249e88e880f64c35f13d4b5bcae9d0fbf5451e7a3fa04ceda329e7e7731b24629c653ddf3602e6fe1f87c2d0fee30bc2a1e168ce137b9ddb0782acfb34f00035740c4ebbfea26a5d652641019a8081f9c230e611bd3be546d91aee52831fdd8ce7cc77fa0650e3dd302018166b7c76bd59949e5184e37cd13dc7ca044670245bf49535020e4745fa3eec7b99b930e279584cb6e35bc1088184a930c088a6a0996ec485d27d7fb6c1e1f92fb473736d76781b366bc59bb9d281ed78411920ade035c4224dc5926b1d44d43a55895faef99335f3d943d595d996207d1189fb88e6cd5fd8cc4efd2ee35db9d4e7b9472e5de623a907fe4178104c22f0adbca43fbad3016d5852c0727ae47cd0b68d470b618c8e58fc2c18a5ecfb4bd029fd5323e35bf5aa81caae04510a0c8ba401e488fb2517b8081bea5fce9569867d2d37f7f39a6a617f2323fe66703c9b10cb964a499ef7de6786574a19f6d2fa8186c4ffc8726f69862556883314445eee5d71855b00cba89fe8f167a685fa5ed6fce08aba433f4d887121f0926aa8ebda7aa2ab8ff072d0fac0aeb43b228cde11d309578ba16b3a4d9e9bfe042c95d8b316340d8d4026397d7e71457b0405a127c62559c70eab953fa92057b7332067cc8d78f5de51ed73d181d840972a4b72e22d2055a99eccf8940860e713020414bd0e2073f428d8e0d8da05a2e9c7a06d1011101f4b27dabdbee3b1769861364301afb4e4f1a177642b7b1e0bfb6d506b8f0fe0b795cc450b8f0aee6e1d531b4baf858d41856832b1121ce4adb72a08ddfd99cc05d327fc24061b75bc61e46097c0ba72beffef2a29da49f1795550cef0a4912294c14d19eb2ecc3d6f1a4f22116301fb405e5c83518224b3c4cce8209a9b5cc405924d9ef9e526d7c273f37c2ac39a904e32c21b300229b812991727de118c6aa5991239ccd2f1d6873c18f582ebfd3209a965d1c945fda5442c2c03d6f5b5e30f32f6383cf84cb50d77a5420ced854599446a518016bfe18205e1e0e4f94e4b4677be8d67f4634fbc764e3dcd36717173a8c0d8de37d1a027290aecfba0b2f021d6a2d8ce18de509e92992ad5e136190f9b276fdae1c63965648bdeac7abd29d783360d54d96df6f9df39907078793d845d754dbf9f42471372112fbe9f01afe6affadeb82ac6b4836e5a186cd2ba54388b42631e0c4320005c747ca982c85a1065a9919329a02f1223654d70f3b6c6ec2e89efc8f52939c4545ba72af62bb27f92c1ce989b51c569073d9959c3a8dfaa85e52c85bf0531ad2df1ccd4ac8686a250c33d561cd48730154e136eddf642577d676cba4365afe2ba12aeebfaf3cb0c2b79e4cb73d705ff3f7ff421684b5368b6bb9f82e4f4e1e15e6ad2dce3cc7fb578331e8bc47e5bbe689eb6ed42d475ab5e219b0e6be9daf6a43a45f6bc8ccb6ab6e7a889f9f60cf8190b4026544ff9aba46065b108939521feeafc4410ffc8bd0fed5c0658e31d04093b4e163be914c1c888d510f451eeb96a612be4e8f2158d92f35cd48a4e07f3bb91e9a07a43766d02b6a325f14b7de51db524abbfb5049f66085bdbc88d0e99cd727cba85aed29e89a892e857987600847e7f1427f2fc68cc7bc6060b7379b52b4f9a6ee7c505c810368c224601b200434d93db978282d2040bf9e2d8853f9c65510eaeafa6d90030d918edf2499de195de04a40ceaba85f393f11d5bb78404601d71eedf1b8725afc729c48c8e05c55e5edca9ca73b7d5f19aa5102f1254e9027dd220bc39fcb05b323afc2e0b0678f7d50a1424992b6df42c6b6fd4a8896110cdfab0557a09c1386878280a17b77634c5620e8811c96512601fb8838035986dcec6020979b348f8da762d94584c5ea837092730f11e8909c108a5d80fe436087f0673aa98665931b0b2d26fb1f67045032b1d0a5e4386fe8c33b5f9be894c3fdc2a7049084be48cc517370013843a2841ff9589dbe3825f59bb4bece54596677f908ca04560479912da944fdb8527ec72f30543d9ffa401b04229d4600a28611d5b8d8a25864d983eeee9e18fe6b83400c28f85c63f905523a135f18b744d720841fafbeaa96fc790e896959bb441600ffe3ca11855290f05094db13940a6f348c02093a7178fc6b29d0170907970b6cd85c309eb768ea54886d064e6c4f77ff47a853c0de0daefb68bc28137107e67e76ac1f77c1a7def0482e5e310864bd25ced98edcf895a920e0f2ae694c96a0bd709062c0fe7d2700f674524deae75a8b683dba361f213af7e8e85d0c42f2b0024d5fe6f4fb424470d8f8f7d6b32c3b2b148f51c940671284c5b008e0426752c0be78a579440ab52fb0e6d1aba4c24134f2a237234ec4824919c8c4a644c68f1d95a16246a2dd758cf2fdc6306296619db1afa3a3eb9f449f173bbcf819dd9270ad2d26ec8eaeff7b20bf9e3ab74ca98979808d93285a2236a8f983fdeef314d5a81c2a73eaab68d29e197ff59eaeaddb8997a973315ce3276106ec77d019db5d68f598a22c5925760a3a2bf74033a815fe754251dccb19ac6942005d54509cf1ea8aac2e8a86fcab4f1f841e8382b3b8f4cda3664ddddc45c9baf25e2509b88fe7f761507053ba33e4854650a7d58a627d95c11430083087d05f1d4c82139397a6b6f0b370ee37faed1503ca8876e539e974ccbc46f9baa83a239c4c2924311f7cc1e09a56b98dea447c3d92fea58afa486a9197b04664ed747fa0f33a930a3cba69faccce0de2e234d88e5b02213948d35e146732c04ccd4366ced8392e7ff53f928d4b0be70bde3ca707ef4d0effd2849ad1c1be0ebee263f9e17701187064221af0ab97ac5083aaa01d6cd05c92be2d091a46d71061814be1c6f64fbcca9766be517589ab5f21c926a55c734b779a7572e8225ad4448923060189e8111a1b9f1b8ac9ca63a2486ed8feaf24285d4d9295fc4ca3d7bce503a6d3701b004a5211e33aa6fd8fcdcd5689cf19495afb2af1fbfd909e42d502791f292bffb5150c3a58216086be16e90229239570aa22567c9d4f6f39df129ecea58b60d267c663deb94537a22e2339606857b56a65afba4a6204b54dee7cf16eb12936854ad7af8c8c9f6e952d70c757a4e89802fa1112717291b7d245995d3f31a3e180bf4033394f3b4014dd04c479194c5e9544b9f14081f031ae9dc6fa2f24d4abce5918e41e855be180561c8eb89e3dd8087c3a46c26b810283a984802bbdb5a410eae9aa2aaac848db937b689c53826908171748015154d71a488ec76135a6f5e5ed1bec1775e1180c0b8ad0b70316678dc24922a67db9eec91fe002f9ed3be7dcf26a9f972b3e738c6d2aa0eaef15563bb68b01995099a6d5398c533e95df53fdfe104db4292a3198f36f98e6af4ec0db7eb4349fc381863227718071cc4bba04bd5099b328ec3830fac71f3c6084469ce659df3accfc3f6ca9ef21a7582e7b0432d77d87fa59225de2f75e2c243b5b76a82fa8e32eec503d1027cdedaacceee1a28707029fbbe5fc885a9446b4819549a2a26cc6355483f0ae97a3b209e01f71e301d53d091c9a5ba135072eddc809817b56fbb4856d774fa527af070d25d714ed1df339a98b5f2dbe8ba7106331e080b0fcec89af87542bd508d94d02d7813100f14fc3e45a0a9695eaab5a145d6490289bb3fc2ae70210082101e9d362f2aec1cc1105461a8fbe1371f149f72b1cd5ae052ac506458a392ffd4406640d2472635f37513792cfda89f9c3f831169c62635866ce632736b231c5596033a662fcde3924eaef615b087d945fa3248edb7388b7a85aada16d06340d2f699e31fb32cb9f6fe04318b4ef42a2de8c269d3571c7fb9a2effaaab62044274504e53adde65316e570f9f0525d0109fef1f2ba7b738344308839e0f35f33e684d088e0141e1b66dab66d81640f0859a0b98f747d1851039e8648cefb5dde6b6ad4cbf1adf40ec4a3ff0726216df7c1da0771f4bb8d750fabc8182fe8dfa7dce0639c037d482e220b2d80956ae48de88766557c19ceb3828bd51e5a57b41e730efad2afbecdcf737bac6461464e614c787ecdc81bdfeb79b538b45813bf5f9bdc2abee84560fd495dc06aba83c40070a51c1244512f864bd7b63a11e39164bfbce02d394b71cdcddd0272845a2818a268df20f4f78e2babb6a61484cb8c1f00e09a3cd0b97e194930c1e38bacf873ec20000f9637388bf0c295342a1ed5fe4e7b8aad3f0903523227edc02569795e9d66a714d06a8b66a30579167de7cf0d083ff8f10bb2ab4cc7dc503e60a85878a44544a0e19f2fe45a2483888629adfbf24014dda27b3082a430ce1e77abb5d7adc8f4d8534be094c109f885fc2c871345adbb3ebc41a017ed3deb288fa671ae4847b4c21e44ac2f2bd712b1838bc8ed1dd7bf86d94a9447542776f6ea7945d81b71a7a39546e037c8c334e4b159ebb301cdf64a08fb2150cc22a0e1e5be65fab0348387506c0fba652efac58cfef2fcfdb211393451dda5836f455e6decd0b6e36f9d233fee7a4d50d01b1bdf45764472b74bbc19ab17181ff10fbb56b8655fd448a87d936339c9e156bbffadb0101cb9197939987aa6ccd375e3d47938f2d9a8a1e7065933dac7f3f1d2c765d17614428e6c838cae49d1306c99a2b3fd9e9cd7f897e78fa5cc74a7c7dfb29a0db5178160e3d7f6df364cd2debea3ac67d4f66b676f1af963b1b7fc436462becda582a837c2283ba8698f17dd7c676620893dccdce122cb677263cd9ca733db37454bc2759628a9e9e2f23cbc9bc64d31eb103efcf91dd0bf722507cc5ff9162e4a369ff6d1d326f3cacdc3b46fe202748f0f940bb8baad9e1e0adf2d12de032e498ff5f32b600ef24a006bc7e0fd830590e9c5c6df4a7171600cf95520bd9bbbabd11eb7bff1099fc36aa7c22e6dca7ec825a75accc3beb54059124b93836fcda187626c073a4dbe51915273872422b0b578ad4718fc21a6323b5941ccd54fabfb5bc9c89de2645246e3d2f61dfcd87d4768565e35ea41272d863e39cb7bc4c39e096ccd544dc6c4e34df29a019f7f332c44b38411b1b9250c04ddf88e4b5a603e2d20e3bade2effb3a1f01828962307523d75ede8961dde6d0c95b7e36067f90bdc8a1c58846d44025854e7a2e1d35ffd9db4ccca7c29338493055e53ba392c1ccb7f2102f8d0797c6ce65e6ddf1e3415d671a53cd1053b59864a828a7c0eb59ae33bb11bdab3bdccea981269d50880702ae59e3d5d9c300d8b42548af52f4ce9a1bcf3392ceacd93a9dbe3204f0504f4cb125b01bcad7b3ca8b2929f4cc4bc39d007a620db62532388716f4ae158a7a9e57ec5a031829a3230715e3635fc4f429575eca6f812a8f65256dac433d60152037f4db6c8c72594ea2b18269fb27b666f883287d0da4906b88310323514f474954b586de1a18cac45cc403487221420e3ef20ef51372480c605eda77fdf18b90e237d3f3843459f1470046f34d478ab2cc65828f146295086f38b1b9e8b86c1970e0e7f10943b4e106900c30ca7357da7981cf1130997c7c78aa926b122408566c817aa696c12fef9677b46207ebf30f5e52ddb1ac93ee22989a9456183c15181116e4cac9ed8885ee9f36b03f233bcba92bdbb5b5a0cfe00cd2d97f28c6c6de69aea7f095f33df03229b62de962c42a878d952c4ec9a9231b2fecd6625a200dc0114f111ae4aae718118757a5df8950cda3713fc002a4bc44bf634a27c8a60ba22a257fd639f091f6cf30524d61741c738eba178b8096d488104525d241c76b8939cb4ff1601ce9ea861ec77ef5acda57d8010a596af53edfd91ea016f9a64d745a49b62cd3e4aa88fbd5110652e29cdcaf3fac29356a0a50f32d194c1a0d5d77075fc97e0e94180d1429ec66e792b7cd85434e14804d2123ab11545b8d7c3ec65e84ba865e2868a01a78b6f53217ce90db45d4757d9d7e87fc816a2290dbbc3701828a685f11268276bef638fdf2e2ab8cb73645bb4fe3f5346e1ce59f4b5be52062117afacbcaf06061ad5ad58134771b2c626dc2a7b559814a5537f6a5906765a27270e84680b925790477e42335ccca26b4bc002380752de6e8739d446b5c7b91968d46e842b95eaad7daa0d6ea160288ebcca84f850dd3779f1456c58e2c988cb5c51f3e8ac04aa0e54f539caffea006825533204b206e03b9fb80cccdb8d6330132fd90664bd2480e7d0172dc52b8053d0090ba82e9f0021bc1a0f7d058b579ae7f75486b762da85c24c3394527b34b5a6e2c4a783ed632ea67ca2a11ad7a8c208677b4d7d2529f57caebbb76625f0c344b9fa3bc3b25a1796f40055dd0e7983c0e954d5eac3dc56da9356c034701e0d514b0c1e828bea189a0ae613ac91a3d0bf014543834c520920bc9bad0db1fc03b13a3adb94b9d02505eebe2c66b31871b44e75ab8fe264e64abdec5611120274d50c66a13f54744d6539c8def9677c45a2030c54333f1a10c3e2080282a0f30fb3585d0875bd9a489aa0ab062d2b206a22a4576e92a61c9b2c9a6ac1615d6e9d7d597df9c85a45936285ddb41e4556acce0eb98ccf620be6453dafe0416c17f50565f83a6df67690b12a829ead2ba92a8bef8231642082491e213ecf50bc745b5d510c86ca824ae0069657af0beb632025a9b25c805fc146fd00cbdb6e4fa21aafe49cf3244399ec14af61439c455cad0c06e6934f8e3a3456b3270a5ae104d41323878df0723ae839b95e4491a65e0786f7ba37c35355d5ba25a43338dfb7436767427333cb16f1ccefb41c89d87bf7ba1e06f15e3faf14033584af3bb58703f1859308583dcea9f31912ad238ca66ff50e133021284c6574973065a44e46a701ff9f7082ca7372c5ea0d5cd1f9794c9fab4f1149cae474c0e945b4fca8520abe2e71fa159ec6dce9fe241150f3c862d15ca0c3756862c13682a85e7607480ac1237f80de972ec04417cf930bb411a641e8b496b388e3a75c424886093ee5e205cf8c495920c72ef766ffabd5137f1a43238eeaa716c0585c5235b905993bb8cf166b80e42d06ce8289202b33231ed95154fd5662fe91ecd43773d228f6be2eb4be950cd781e11d70e95669e311b8c50935a09afeaf87f4c41b82ddbff1abdb2876dea0f1b031da616bb7be24668efd80c2b24fd6ff4ac0199356a8ffb17293541428a7e61bb663308a6ae9236c93fb79a0a0e0418a6849772452be2fe36bc0b8a03a21352699ff7153edae04ccb7959fc0330d896d3ddd9943ce3af404c025150255fc2e513c7e38d5b805c9986caff67cf0ee98e24790d8e2d3ee1792e605e5c97f1f2aed106dd03259471fdeb4bf4ccc8d3192ed0f73761ea48f1557b6a933a2a13be2e5c5580f7a4a60e40595e15464ff81829e1480c4e3d72625310404ba076b2d4965b2feda7e81628f6dba3e9abb8ed3a1362408b542f289d3c2d4d7bc51f1cecc54e318bf0618f83b81254e71059ae909f1dda3749254bc83f09e9c5b2348f2659d018e9cb61d4148126b3b342833c7bf400ae7626a28dbf07be11a2ccc38cec96687a7644b4b41a7cfe61115777ca0ee3c7bacdfdefa516a9e7da2c53ab0d5393c4a32e8ebb1929966ebc34ff2b9dc6d80ad03390aaa46098d9debd740da47b74becd67111d644be2a32dd28caf2303f6dd6c2c37e1fd8f5aabe79932334b3842747806436a896eac3c8378f953ba64b0532e5ccb6c60a68a6acefd512c77e607032c03f5b3f2ac65f04c3866e5d5035953881f5851028a8a1ae0079488bd635299e0512f27bb919636fb4b3930c103e8b8436f457f705da745956b9a8a042e1d2601a49ebd5e7516dd083c90aff7e001fc37e35a5d89a9f2f919915b43b8c1f8f2d2f7d09c93268ba67b2c351d50e38b08bacd8ad0a61d2752ad24909b3c8713eee80a3a42b4e27f1b31483b086b870a5fc3a9df9da9bbbe0c203140bc825ea26efbc07cf276ed8cf1c6b7866cc6b0780cad937b734e1450fa17110ca6eb86876f73317b17d591fe4e51256a758a1117e070958f8ce68ba2d0ae5b713205f0edb8d1e6eb7a2e571d4ca5fa262ea1896d2112871ae02d097df67ce3744652e8510acfa7aad89efcc1701a6f019e55bd6a7c29b5c5c5964538c21086f8c155a6123f65b3cb427910c16739e433c9f416fede03cc67d6ce81dc75b8efbd9c4007bf91a5b9c12332416f1679689bed9a3394dcb59cace687af941ac0034a112881fe5d4d6aef947a51eed1021f452ca074c59d37faa310c138077557afebfbb05b4c63add3865cf0a3718b1a96bcd96db3baa8766025086c89d66d065724b299dbb9f1e6ad116eb40b395b9012d452b9443efbd69c29146e9e88552730081b5bc0fe2b7349c9a955e77a7deae45e67487136c2ba53dab7b04721f54fa1c36d24ff2b5a2412d9a0dd23f2b69c7a5448097b16153a549026ab842a1b3c39b0e354a1d57bdc0c65f04761d73e67050690009c0192a3c4d5cdac16b135ebfd1af481e988b3acb68d1c5a3839cf41daa9718f15ccca710109d4eb36063fb087129f13dd5feaec2ea375631fa6802280b468850d7e9ee8096ce9c7b4e99d345b29e929e9f89f586558097dd10b58b88065fcd6c3d359dca6af9e134b86105d523733bd955108e6fb0876298e6d8dfb6749a0f3012445ad4326f89b0fe34ea0e499f0dd0f75f83db80389388da33e733d0358732635880914c11b63ae5507cd0407f8c9034e7d934703d7cb154d9925322f8da9aadec1ae1260d79ac2cf0b48111ff2262afe88eebcceafbde85d48340116e83944e026f6f8d1edf024c453a5834018ecf63ff77bf7e7c71c63314d0db694de237f2c335d5527417f28f85f995e5bad033ced440ab41ac3e8a5990dda0c12d3b8671c827c9302c7a7b905d53a0fbf2452a69e60b12f9f5208a0cfcaf1d0eed1e482476977824c027cbb156db7bb9263bec7f24186e3b2059de897e81556cc7645c1c526b7cc7c0208ba6f4b55da80f278f6b2f5cff8bda16269effcc771459ec7ba46daf37001dd135788e28daf6bf3070e07204799508bd4aec0162c92f07e6dc50a612347075cb4019bcba70b5d6e7ed6ae2db2ae342dd35b0c08b25339027e6f4521e907f003d342ae532b58313f1c8a0b62b2146f83e10c6f491497cc7b2f0727c64b269ca97dfcfad56bb0965c1d742cbbc24340c58664cf163dd7c73d9a3d7b1e8e188146c89465e51a8c37bbd19c292916305d45085ecefd244e8187280fb3d774f2aa66bd1ac469af0fb8b9502293ff3bb497ea2b26a77076aac30694a3ddd2e23700f93b37d16f18e33da5009178a0fa6c5cb0199f8cc3891a2b9277a0c4f79ad92bc2e8bc650d17be1984a81913862dc1bbbec474be2fb6ba4c24fd6b9a09b427c8406857224256c21e0ffd62d31c4a01631c1847db929ac05b4c19b89d18e8a6726e3cfb0369db3745623a034211fac8297019a9ff96fdf21dfc53df93323663f3f95235e8dd518b62bca708a124e09da3011ba7486885702263070fdad57c2a59b71946f790540434d4d59c24e256924715f91f947c3919a7fffa290803bb9a5f3163d6935ae1545630bb06022559298b4ff32613e109b9adb0f8f3df1e736dfad676558e030d48fc29308a046571164320f5452b549f30f4386de720def0b6fdeb974bf327595111211a90f93c8b691f65f2ff63e6070db3df11681f9fe203ffa9cdaaece31e82d8047d641c0d99c65428079ff90e47600dcaf9ad508f4ab38f1b8a35c50e245b8d76e7577bbe82a1813415ea5a8c64f7ca23331697e0125ca351c07753b31a954b07c55188cadee955ebe1b94387c2bdfc68774dc027f6498f53f04089a2feef3e31ee6ee2e0b7ad52511172ed615899afc8cca12b8f54bfc9b44e1ebdf1d82e759c3f8279b781a6f82ae2ac20dab59105fa2e79643f41ebed94a64372b16ef8817dce4f6d3e7ce264e56da8375883339eb86ffc4e677e3110a35a0e73568d4faa10ed28a29c0adbecf6eac36221a5fc507806b198e1dae947d7c8da244b5b5440357c8fc2a5111038e9cd3f243bcfa949a8860e5d11c39ab27319120cb6cb59f7f2d4ba63d2394514538e056e35b2e53110e9376ccf0733e7a24bd24565f9e50874517b6515cd4c01a3ca3d31a07e0a0ce490bda942f71b39da4977bdb7cf0a8130c09573d5bd78e27c55f7680ebb68870395bf11dfbc7e68bb8ba6071e9929911fb6d315de00bfd7df9d98eb094eb0f0c4666fcbabd8799fa373f2f91eb3e4145ae864ba1c421186b09d8656ffa6d6a7ca0329458f3ca9896ce9c77c4d89f2a44c7e7468f51db27b26097484bf2af59931a8d69b12ec872a3374033934891aa3bec8c8dcc593c6a166ffae8ea52f36a0a2245cc0fb37c22fe63165eb64e2f5484b4c7a7fb9bbc08a963f12922428b65ddb582e34fa235e2ce83857e18d63cc4cfb1ec75f4ac404b30c3eba308338114aec27327e00733f18e83b73678cf8d472fd67d579572d1ca2d19e109df5a52d53172bb4a81e7a4ac6a257577b45cb214d40b181b789e3547c408b7675cce8d47209527adde1a5138a9de118e2ed5c82960c1445fb0fa81bc2f26dc99e23fd90a3b63de81a161720c1a383611ef252792ba6ab06174218a9dd20dea040c0898dad1603aa545eaf10b676e2b0f0d672c9fda6fd55d34cc5133802a4159f0f94e18429b082371465bbbb512a19753d3d63f320ba3b7635d270607919368df7652c785b88874dc6ce160df669af14cdd4a50a9c2e15e194db227315512d72515fe5600b3b20e6f5805c4018cdbe07b1b7aae55461695def8b55e051b1d68a3c94b59a6ba13bab49d3b1ec5d46483e4f8ad8f514329a4e67e9150696244532987ace8306b4f203ff6a642cf3fad974d866893c52c73bc57f81a5943703c524bd9a2523739409ff939ad5d632b9e532622f7b5288d2cb40b9f70f08bf94fe69cfebf9e92540583d7490e1f98eead32d336047f27e45d11d8fac2185af7cd664ad3a87e6f94306e0d700f9cc0c7280889ff80642748279daebf1c0e0017bf38c0464b1458b719683ed54f8f1b13510e442dd63c46e33085bee6dde75b14ce139dbd9b858248e942b6c377a002d0916947d6b0b5b6c0b46abd30f11bac1bccac324add618ed3ac379bade6fad8ea4d23189e2f3846f6fef9c38c0cc039c5e15f8b78c222eb426efb376bf32f712b9a403d8c25f9cf00d3a9e5699a679712a652a635b2f56b938183d6034d7d2d89bc5c144366289fa54fb891f2459a7b29e4c7399e4eb5ceaf2215b25792845939eaa91e0a27273f0c92b5dc0836f8e6634e59a672a4d5833214f4d38d0a83b4d1ba3e55b12394a1537df6ec8d453c592fb63a6578d4896324831bd49c48fa1fd8087d1cefa7bfcbcff137f699b1e72bcc0bcd25daa4a3758531b2cceb22aade14570880979d157b7254b6eb45fccf31719078cc5c8287d3c53c5cc59d2f14b6e5c6569e7479877e25c7a916290a14ad6bf222b8f043f2955604e86a44657d5fac9bed26d4eac5191c67bb1aefb34581c6d991e5d27d9efbb9ce9304dbda09989cc6026cc74d98090ea2aaeec3e46c7d952f153e61eb7a65fb2aa4774ade11a1996cafc06696e8af7816088105b17f6e3e84561b231def3b137575ca1a02bbc6c4432a3fd53785b5616c19265425613ce2efc7f9e2052b737697bd9d471ef3b358283b58d4624768514a371eac0e04212e708bca91cdc3940c9e339c761976962097d50f42dcaa5438422bc5aec2865bf5888809229593d30ad2117e1c153649c3397338f941e1132bf2c4e41943d0bd7cefdcade768ec7588265002989373b55afa07534b2f1de2ab1591855da61e013c250b257bab765e256d01698c30c78fa287e23b329d924e20a023c6b3425c9aa53d4740324f026a65aa35807275b82a979fd1e447853af5625e63f6d2d783b97917d2077a22682dd3e2a0b4cacba0565097248ebbf7bdfb5867391d8cd3cd57a3e0f265646a640b775db5d84e98d4c5bcb57096999170bd514d0cb22edfce18e9805129dbe9df76efc9926d4ac850a3c0dbfdc9a72f2b1a2f5683237ce2ed89e7888930da80081b824e355a22004d69558f9ccd0c153e64ef38640c26ecacc84285eadabe0c7e79858fc3eb3e567e4a2d157faba45f05048ab19270b8031a0547923839814ab9e8e4271425a81f04351ec215305d4410967ffe9d350852f8746830b47e608f1aef54b3c54b6587614776dd8803bda4e261fd63a43e837ffabc11888bd4fafd35d551b9e96021a80963be94e4f36263a4aff09f85f369a37c3e6f697e8fe6c09aeeb1eaaa858e60b5e01e0172185e84175cacf3bcf7e07266f7b259892e0742c55da0ae55af533ccd19665a4a9527c58b9e779efbefc2ace44ec3338858d8996583f23f66f9d483bfa35f401998c152591aaa2cce5f44d29dc1c6767dad070f28c652b5e6f6505df284d0f51185f759ee08d79cce05916b7c589faa971a1bd2d7af1467f7644091f7dc128f46f1bdd774fbe0ace52a057de6d292beb2553e3588b746d01d7299b077f11878457b74df57a37a781b2335d3c362339266d28c9ee0b34e52ea3505306c0640af0d7cf8444c311f2a247334d38d22e0f4ad6e94be44bc4b8380d6c92b2de8180852458a229a0d21d775a05447faf8e8ed917d2415b0934b10d95617458a485e3865c9accf3a12fe6e2a7e45fad6eea15f05f782f12ac4c2f5f0d86f9a205a54c8a25856e9e30554a00dfe908feeca4b2c95ca7c6ea3ffa3b29d1f9178cac1698389c6f3efe56dbf245af9167bdfa93bbead964c07cbe762f60d3aa3a8eb4728b25d148ba9cb8245452c7b05b7b34bf56668f5b02c8cd2472c8bac2d39591fd7590cbfd352d8b438c33083e9edebe6368b13aea8fd9c0c7116750dce25691cf0e84001efd44efb4c19e6b4b7d535e81b0f20a7f9bf13e08801377313b8fd5758ea8f373634ea2f2a6740bae62801ab460466c7e2d0f47f95c9578e468a4876f6e8922b7e2e6f957de155e9804e9945f80b8c3690ea7252a4198e54fdef7c231013e2966afb9b40144b98b8c16b8f7e4c3a4a2b262e4ffdd2c29f5844602e2017361bc619a60be3ae8ad8d2a1186d3d8dd9c1a799d8114f61312fd31afcab5da789642c6687ca8973816965dadd0248ad7a18c1040b4c0e279df9b17022b0d8e095b829f164cd3b5b45c90f74625eff4857090e6fb7835baea56361f1565fd3cc8651a52200b4a9f2fcb4a7ce56513ca04f1e444d01c82f8bcc5675d29c08096ed92cf3dc9c70d087a64735835b27c64731f586236ff658c389cd741b13ba746b955d32ad3ddc5047e7257d8743212cbc21790bd7794571b6a06f97dc54b90bb79891e5a4935b6aa7d25b90509ceea9a7ebd3de2896e542eef22c3bea144d8a1016141fd626286964bea96a7052739dadaf01934667430a2ab1393ddf9fa93a874d31d5c8829d71b3ece2027a905c4512e4be5b6156a93feb4070c2b197726ab773691b1c02e07c88497b7640fd06d805de30633a1568fad82db99a161deb12830b6cc750158d6bedfc5b5ca93aa4246cdd1261f9ae1093ec4d42f2d9538d2ad95ad2b5a2f745781a11c1fb5accc646c10fd9208b194ba9039983b3cd3b822f69dc0823950d5e233675dc5285c05af8a29b60cf13e1c7587ef8281c7d56b635a78100cc19ad643f9ac28175b57f10b4bff8bb80d5f22a544cdf36dab819c1e309a434b94e7d2a04f5facb17595aaac37452c6bce62bced1399dfafda37d1f4f64c829537b9a7af07a084f825d1f0ee104b31a9b880ff938caa84af771a812e4a9a61606f49b37e11c2ea553f67468bae508bc8009509a7b76f54ae5ba6fbb3b42396e07207e71966b6696a12ac40eb62dd44c738e49689851195dc716c3346c5d1fc77d8698dd7bbd7590221d36b7856a3d3d583df2adc7f39803e44b517b9291d5519b346d26e1118f68119e8a96990779534171328264f1ad8afe6847a5bcbdd40812def75da14abcac298c6a4ba9933b67330a2d716c09fc052f8d36a966b4df79ff9070613fa7695415662d14f71f115a4b71be65baed909ea9de5f97959e2e26e9efb5d71efee6101ad95c81e9ffdd4d49fc59cc9f5dc3114aba6e56c3a83dac8f9d60fe5fc986d3c70609d0f7ea1a169b09f70a09edc36cb57b0be265affffd299c572cb608c67eb51fb865f848fe520f066fb52fc1d9c3041276c681625ce1bc8dc48c4612dbc23fcbb0daed5500ee3290f8ee08a01d20d4010b45b246783b9e81af55df8e8806818d3a3044ae2b9c3c2ff68b4bf8857c719411e4c7f31e2201d5cf804df60a643dba66cba54ccfb39e51aa28c0ca9bed65c31fc613b4e702ee0525824cfd264ad7b6b6e1618c07f821aabea2be227de0497f721559e65337e7f0f4d71b80f8cd2aa930127fa23bfc199beff7b5c5d8efcd70c0f2203546aa885e1fde1ff6b969cac786069d8b5d3cbd9d30c76154a8c177925d992a2bcde01bf21dc2f754203553d7944216aea8fc113f75cd49956b036f651f8b0ce0c89cde08febcfe414c13215772c9d3a6aaf49b63f07636b281f0a581bcd902b7b180c6b5fcd56e1f7808e6bb3ea61b801ed8afe23eb6b7a792b835632135d247f015c0a5bb6a20bd835b29a1176a88c7ac3fb7fd48eb1c8742cc0203192d060b1ef8d96c7e1b4c6b202792c10b9e92627df4ebdee074edbf15ecc8cbe8c89db5c18f40aae3f7e7da4df1bd86fec7d1d9f9ccd0b67001c43d07e83f22c00725abc4411e43b25dac3e8721074ad04a9de3361f40751580db1164ba13d86645b92b267637637f26cc345b2c742b633ece2871f5bb137b95013f1fdb64a9734d5c75cedfbcdf969b4b63d7151f235676ec912dfdaedff248ea3b7d0a3d52ebee0961b853d1da8e3b7676eb855b6b1e8c5057ff6061726307b380842a1a12cc4eaebdd8b1359898ff4d13a9c2f7c9d306afdc2fecc18231d2385f1d09dcb62070756537b549c547d65bb015c8955e9a3b448bb4ff7142ad9238b3364f44134ec002be3848a97118c68d10407b640654a59b6c6874dea8dc688e3f3b45d419e525e7f5f414a3aa9c5b1e532d1e3ca922d102649072ae2fd888e51c292f768f7bd6d9fb82729eebc4a186f1ef0e2cdc635de8e9fd2442d0d52ab3b258804dee819b9688aea93abac0ebf85c45d6bda633b3f673fa65760d15a2ba26d3f4482eeb899446eb808f904caa96918c079f796d0591fff364636c9146b4152fed6233ac1b4fd70c9d4b0f0f30a0e61baab6af670b91f145511f43569c319de9d480b03ede13b23bf83ba46f021ba99f751cd88f8f2f4103d87e48eaf79712ec9afc20e383f5bdbd1fecf7d585669dfdbda7f941fb8912e5e19bf8d6bf7747b1026e7669957146952663379d1cc7c1cc9395290b79cf7868c888798141d6d37098378a9c9dd15b7f219af498a101eee25fe64fc41fc419cb693957f39b5f65b595f757881337e3374fcadad8e2089fc19fab2cebeb1bfe1a5a6bc4bb96c8f1fd6f6e7470d19c242d6678273b136f57ffde1b6ccffb647685c973514463a37e2ad09e86d9ceefa2994b25ba544bfd18165c6858314f809492b1426184beb770c5b45e2f4699f3a4deda3570ca40e22a7bc6a7b92e03bb313bce106433df74376f9f1e555617e511f2a10ac64fd3b49bc80d6e75c80d7459bed9fae84459780bf32fa7bc0b6d75a0b5c0f06f2514b232b24e8b5a601158e9a5dc8c48a98484b0c95140c9a5d245925daf27afa7092c9cb0600e048ddae240f031224539c4131ee7d376c624cee6840994742318be7a68ef406371b20032993e2bbaf75461278137cd25762e0246715e8819e8368bbc0c5748a2d6a0a6bdceb417f3e1fa7fe4464196b9f7e6648f6546f97c1c667b3514dcd2d85e0356d1b510710c2f821bd533f553a1cf13bb023f287eaf30a5a338dbe788781099dc282ea4db69675b955608bd25c7a9d2e9b21788e905425311ae73eabf860f64a4a08b1b03b956654ae5559ac0ba17b84b3355262313232fc9df2cfadc99ef5114f5fa47e4c62989b4b2488f98370d2cb9d1e1e15bbdf0bb377badea86edc148a2a448258917fe8ac628ba7391e5cfd80bf3222d16e81c2e0522c6ded4f7550d56548048e7d07ac2fd7f8cb13fb4ae178b69c885ed253a48e70be79df0bb79da51e1848a3b0acd758fa02bdc42f9d84687d5d876281fe447caf0b7984c78893a1abb58e9e85fe74b230e277d2fd56517e093073f15c3335a0783d065cbbbb7ad2c27da4ba0532d3ba9128abe407f72adad48095322f276713165c65087161022c48441de8cd8750ab2cbce95bc0b9ec34236f5cbd29da7ab5e9f4dc2b290d6d57454fad345806c25df36d53f9b7ead90be062a492b4192695b4a2b3fec0f8b2826a0746155e362d468eed1bd107577e894b5a1cd60b7abb145fc398c206a77c3641a457da5823e687e57d1e5814933a64563ab2df3cd078f672f94444df2f746177fccc740736919b86f9fc4e82a6343b8b6cbd4db89adcde0f2a27bb9b4c94219bd25307a2743e9a63f3b2d5c88e4be965d13751af77036d9f20e09185cbc1e185fb372f65decd447c83c78e98bc6b3bba9071242c649cf5464640ba73370b1e1262242eb9b94a13c1c4d7ae621b59703b24ae8a0166f6bb2979b141a3b1d3a4383f08d2c4d81e170992dd0d20865da94da67456042070930a6048d5c52d6fa63b304ec20a846801c8585c11148137902f2a58a4c7289d38b66f0e1c78d9e3ebfbfebeb6e5b0e3d968d3ca80f45f615dd7e6417910409836289aca177a16e08c293e359415521a9aa8883e55c19b1f35eb143280f35ddbe0a11a72d39c40a45b64948485878fb103609b1a99316c7c289c02644d0378d6dacab946b7f1fd31e5d8949814ec41f9426c2f4e15ad562c663e41c35ba128fefec59b9774bd4caaf18dc19d7c6ac3fea3a5026a1679c3d1e8507d60bdba8244db71f5ec78faac51a77f47fdbfb012f6194aeba951862962e313de947aee315df23082506390b6f88c5a55b76ce8d405c56a68f0708cf2faeb515f38addf574bce1363b4bfda8579de3c7ecec59d2d1e45e0230c63a64248388df24673c41c3eda0f3224d4c92002ee9329058bffa39793588cfce1b29bc1f810ea041407279a67193e89d924791ae608942d91111402952c8f75749d2c62f2487dcc9755a14b9eaa9eeaa8e4696a19e3f04c0c09658a8c11ea8b3cac56891548fed4e06b7aebd1b1e46da1ef05210a3ff24f665c07d6d03c1c01c1b92e07b42c38f3416a6ba1437a001a85830fe9aaa74c7a5cea69d4b6680a787f2d3ff8953a9268f093fd990384c9b14acc944033e971c1370be2d0ab18978a69f6a98a743c1709082b46ea198926b7c040a83f90b3134970303cc29cf9336d21b0769a246f5e86482c5fffe296bd7f3b9d6cc6c8820b7dca04f273c6f926eb54faedf772627954d13f94ea2ef9dd6269cfca8e765c355a425fc4085cd94b646e5930cd98b42e4849e6f8e6745d658d94af6cf45e8a678e8e8f7700f7ce5b3ffd80c077266671d8e69d03886261c16565bd986e2d63082e2b312ed51899349400127ae1e51eb3dedae90d08597f42d936efd8a39ab1b667f4cadcf2ddd316b20bb125cf62afde3bce86f5fa36e841818e375927772a3a6a09129e6bd2023f8e7b6d5176f0e09c218f14ff7b93a78f0e932d2a14c10024e2209a43577edb8a823827b126ffdb322d569a7ae794bc73e6ac76b5f1e994525db338b6760e9473684dd464692360e3bf161dfa48c859f64be3aa1708c6fca921e3ad2ccb96db2437ec4c3b40ae8eed33dc4460547f18d832978cf4d5e76b2313af0dc08c13d3b2ebfd2e3ca6e053f1a927f75d187135dbe1b5bf00eb19daf8204f75ece7059838c6b6a35758014bfe8013a6b5f477e742140f0c570424da43b2f3ac1adf92558f297c7a0c388648f55480bd3f66c9e4daaceafd3d53bb382da7f6de5918f84b160d09f544d805c2af67ffcf398fca3c64220cdc38568591991a93b18a72664604ebbbd830e5a2ed1f6c1d42c0784ee18e6cc52f8fd951340e79b8124142cb2894959bedd35f12472ec3bbb8785aae5866a30255c6de72734e3e40566ae16fb54b6138cd103489e364f7a8dc7d4d8322bae6273ed5404805290f76357551e5e282834a26e3680995da60a86192af499803c9ff6f90c5af1d8e70bbb2a418a42e405267d406574ff81c6c2fad754c9281f130b909d2200f7a06e7d411884f99651c4a4eddc4acf1616bcbfe4427fc2cb0dec68302fc3a85e6239a254f2d6ed98caa45f5cfd42aabd65453dba212db323b4fc217eeeacb0cff31df8ac4f05b857e45be93d6956e87f5f61f09d32e11ac09decfbb8f7a9ac7b8c1a351203f5454d7206cddbf9f107ab9bc01af9f3cff21f7f9068f9bb2f675b29d96c4bb76ff378c69e696f1111183255d7b5e7d674401c3c9186dae253bff7aabaf00c8004d7c5f606b5838f8fc1e14ba9f59f505507f87b7d5bc7eeef52cf161b80c1c47d26dcc70925d50705aec3e1a0c733b13b225ce123acb55fcf0b678e1f70071890398ed151be127bcfd0e37ac241b5e8e1b13a4b866692bb137bd06c4f94ff00d6209b9b8f48faa04b81b8373903ee35c22f01ebc37e09ea5b0cbc51ac85afb340bf201ef59838d58aeb73f37e2c15d14cc5e3878bebd233c685ef59e637ec3b59b4dcc5be76559a3d39079d37d890325c2a771531b5e87146c27ffc7db7801c539f934a8064ff5b8f04a13ea769d84317cbbcdd041865f8ce69daee37a9a760592d5e763ad23a547e27f8393b3cfcc5d5f2d16140c6ebd7cd999868e630178c1ae6ac78b1f427ce747f921f084177e5f89fadb46486237c67b38783bce9f72ced28b8a0540f7f089a191972398de7451ce60082223937b1def20d2148d4aa3767b5ef654f1da176f238b245ec1b31cca9b805de43e5fa81188304c1eba16be8861ea985b0330d50abacba9a20e7d9204d7af7e259cbfbb28c5593a300a078b9816ee3cfd98b5f18f5466dcebc7d80d84924f5631637a686f4bdc39df0178cdd01a53b0db1772cccbb40ae55756b4884bef44bb8c3e9c420fcf6d6e1331968b2474b095952eb70eccf5825f91a0f06caa60b9301fa6da7b36a16433aab783f0e6405533c0163f5bec127faa237a8249b87c187f394996f62437ac9db174c49c164c991be4aefea19556de96fe1e8430615a8d7510f9537581bee861bd781ba18ccee0ca4e4069e599d536726a9db6777043041b0e21ccc63dbd95b7caa185b3b613d7c71a6d30c1bdf26e32a778e6ee2b96c9d30ba6dcb8cedf29c5a7ea62658a7c85baa30f9ff587448de36562ca04db6ffe56ed6f50d2b3dc29b42c166bf34d66b761bad6bdc887ed67a7da897fc01b1c193d7c88acf6686debf6d826d221e26912599d5460533371b28ab0f7c1aae9c22972c41493fa85a9dcbfdd661c2ae1e9e6234022afac6922433468c33915bdb8bad03ffe9e7b54287d6d0a14aa98bb81c706c200065db7b0a328e3cf85337875a0bd0e39270a79c97e2306e28f4f83c0540ca059e74dd02945ae74285e0aa57f657dd3e2b632beb382b9cb51d1543ea2820304d2b56f460f972c8e352f7eff7925e190e9be877f4ced4f766835b46478d000e88cdaa360df42691e2eb16e05bc2e8e7629b9f888db9cdccde29734597b6616ee02d13741faff16b2ff168ce0270a3685be6bfeb451df23a17af94cd28081357e60fc496e58d912d267fa70850e31cd158f13143c5eb4386d65c62dbd8013992734fde5dccdfdbcba0a765ab0bc7f3c616f3dca76f16113da3caeb8831f91aa154f67bab75c1ee45b3368ee66fc5cce92cf317f1f30fea9c41e244a2cb8f192dff987febb7e7a8eb6339e5785a0788c199811bb2ca205c54f79cb46832c7f8255af0b346fe9032fc2d3e2f7841207c5c4f8880547a358478a8f360208dcf8d0bf97a06b859a1eb5800deb2a8fa50c9e36fa023564e7e4ebd51687d926460fca24e3970eb8c85198881bb360ce351bcfcc6457a46d2f13bf21f0f0e130721223d8a28ff89ff73d89bba38028fb8fe3367f757c1583ef6de1846ff2296b373bb1d9bc53976e8fd0646a1ae02fcdea5852a41dbd6c21c31a8f6448eeaddc0fee3f1a70712d3a5cc75d53ca43faf42fbfbd421850aff55294a6ed9d0eac85d409cd5fe64361bca25401c16715b1005e7680808c5696a4c13d87ffad779a0e483a9d6402f1c03204a17815fc1829598d6100c7098fa37d0a49781f1bd3fcc1729f3bb1df5688e167ddc3dcc4d1659643dbb57ee95351d45b04fd6aa073c0fac7f4e7040f186ac8398d130d2ab583e2dc9324dccb678e55d470118dabb74d6568eb75bdfcb7c8375f1fc23265f68076f701e143609fa9f2daf19a84734fee1087b9a8d814c15e659bcec94a559b165ee6dda06a2e5ac102d7201a4244cfa38e92cf1cdba7f7859bf0779302db84619b79d976a93a8f31ef4ee3108288973438fe8e878f2ae9921832bd6b3762df41879355f785b833c8cf4bacd1a1bc2bda9793360531c06f6658e30ab23d95cc78d89b9d17802f47b038a213ceb63ddcf35a79595aa7191640dbb2f69fa18782a29d6206815e708107795cb7d50d33395186ddd38890ff06c698d532c43ee5e9cf3181c165ae49924b2a9199fbc3e4d459bd26ba6183cff8394a68b21bd318f00105f10b57f15fd7ea4042fcc2b9c4ee27ab5b050b04a31b493958d05f1ea7ff4eb27aa791e0939335f29d64f6c0cf652cd1be1fcd731a96680ea8f04b8c3fc7a9d15235b7a193d3bd7858835b36c6d41bcb01685f4fc943af59e509066308ca3e54cb20bcc1886e57d610f797d5b66cbaf789fa567b88c107a48e83cee1ea6816952019093f4ff04c6a832ed2e56a20027bf74230991ab5b4559a0ba8eb133e3fdc09e1bffb40aaae7dd7440f1b26ef832d00240c8a04c94dc67bde4aefd2b59ecb7bf322b6c88f25ec457a9e7a479ee17b027c93b8af39442997f2729f07ce6ba5136e179b54c48f6f98ea6491c20e86c7df3f749f6ae333f98a895ad774652ac674e6e32d2463eab233417d78d97ceee92a5af18e99ec3498b3c93b29f5750e49e3df504ba1ec6dc60c2195b43c38f00833cdc54f595e011a4e32968ead41aa0ee6f77af368dd635961361aa877a67f83d3f0450d1d422eb4b1f58bd50b03b11d7731a9457330e4fdfbf6d01cc77d1040ed18192390323dded1f76ce5e97b8dbbef159c4e7e2fb0d701d0efff39dbaf85056a9c5d87358ab478fd367f27a607c8f6b168ea6227c9fea6b8804a2bb6f90ccdebb6740d71e782922af1bcf990f15b5b47fad5321bb64d3078c281715912edd6c3cdf272ca7430410a2ffb862235d0fa95c5a3e689ac552f275ea3d332dad824c624be2c84675850e9357b65b9ca8fba112d677d8346adad5c5071c6c39fee0463038c56dfb5f3e239e4e79677a18e9cb1c15bc06e163f2f63762d97870b4ce50d1269e8810066a6e3a103b9b85fd177ac1798381d1e1d2a0fa3e3c9a9ee32717b56bf82a481fa897aee3bd094189bf0449ff936acb1269c52589ffbaeadde666f0f4f1d51a4775cd523dd5f3bbca4426a4f0f0eaf00d3abc1968a0fad748c20dc64ba0371fee890fd555226743fa9400dfda6d7334bb7c946b3e9468969288bfd070d04fa8259ac85fd59259e2128d3f0d37a0878c4550c300fcc0f4ce8bc6f801cf4ed77fdab8242a4e9567d827030edc7168e6cdc45c6e2b49d02cfc035c609bd79c701b505040160008d7f863fb60b25bc666b94c0cc8238f5b0dcbe648c32a48c14a856cdc819b113b3c9b4a84bb5915ecacc933c9907f1f27224bde3d298a3882ca6c1d0520f2160f5d6a43a6f5dc72c8f886a5f2e16c0f1417308d52db5b3b8a6f0df104e758cbcee5fc4fec49c69835258aca3dabe0ae083dab67702561a091df8a39bed8bb83d02a2d9f33852f2d72ef4f346b9351562c05db66ba0374dc92888ea40db3ec18464fb4e1367f4ce13639e552d9ac2c11a93b013f0180fa486382d63b0f65e6462e308dd5f2528d0a885d27d9c2020b6819f8b14c38697e0a5268937010fc040fafc2c348dce748f2e11d3b468cc9cb2a1a668ea90741ecaf37835103e19bffaf6a7ec75c34ec9de8e46cda18bcfb56305373ccd48870020479907c468b7339ac38472c7172e53f40fb69f66b1fb47c12fae34d1636a55f69e660f203c3fd498531770e25a3d9e51f2feda2c032dd2ae8c21a0d476494dca518c1decc123c78c0bf97d2a0e29deffd86c5c02930cb32dc3604871be2eca4ada119da775baecd40512f16207a3435695cabdced70d7e71a156661b89ba41e685d3969aba63129f0505631051e1c452f96efdd2777c61fba5742792f35360c5987d34cb03b5275f4cbf8f46c3343c1c82a70bf55a600d335fbd6270942764e740953494791798e830af3b866c3a53c9d8503b15c28b9ad873059166c8afcff8334ae77c8406c7610173479f787574880ab1cbedb89caab97e55d65e62b688cc5df0f62d74d5b3ff6801d79ccfc77c13312de865b7e1581ef199a0ec2961b8607f44f848c9bd3f1dca7eb0b12e7c6f9779bfd1d01dff9d4fc82f35a35afd1787a8b06737d03f79c627f5202a182eb70b4606787557e1454623d30eac8d26d8eccdfddddd726cb3798c4cb13c9e62aa9a8599de6d789f07822e269b3bac4ca1f7cec554e3f7040b62858984da0402f1b9026f4b67b8ed593319016e99e11b7132211d6e60c99fa1950afcbe13e0cc7e8b6c10694c0b0ec5bca945ac53d98ed9d6ecfc833b9f32f1f1ff9f0a4dffd4243d14c5575bf5316eaab4b2d2f10d543d0001c9665f0394cef4a87517cd3961b425128639195a73b5dbccb151ce993aa597e2b2f50bfe42a67038675a5d2ef8101817e6e3600a81b02b60571cb13c4180c2f393cc3d46f500e65abf9cf2a958ea13248bebde9b3831554639a5be53da85a82385a424051d1f66d8c3d350fc5f82fd2bc8ae05adedf1c83e109b8231f202d8197ab2eb7216d4d8d31d7867ce69e1cbd7f010807eeea3a894be46b8334da5baa89451d1b3749f8a2d0267d28d7b5d9e80c0c593f805501a79538082298e457c279274012f3a80ddf237588e770fe6d9101e1d9f2eee8a79802baeede09691b239fecae9b77069328c33d0a5ebd83a85a1d6ca8926ae3ef84490e1710262f7462e1aedbdbd58d9b6d2007cd987c60a0e814ad74884b23b7048bff036c55b27202737e76aa655680bf70599e3f37f55db857cf02306e71ec86f496c35f8cd54aed024521a12b65618f618a861ff63589ea112b2cbb2e767ae51fdcc51788f9f8c0fe585c2f0e21cde63187c90fa3bcbd1e4160a3495d39f7eeb5b4a97e25275afea4f7eed04ef12e09a4467960adde570e70c8418adea9455519721199ec4d6c8972783ad3e065e09d349a708f34820a95d601ed21f45f2da106149ef93a59cc39c9626a8cfb118fb0cfe61d18b0a502259b5ac9064581aa5e02f924520981a6ad226842f6bd65d942f6fff5f448c26fcc292932fffa2d904bf8c1095c7d9a40a44003f87d4fcbffeb765a7bb742e70a1b246a042e0fc7a2093fcc32e5bfabd0f29c9d9c613032224649a5eb7408bcbcdeaea5945f18ba943767187a74e1d1ecd382ac6b03ea7b26f7b6b3538acbddd82d74d5e35e84def279b238ba336db4ebed2d8e930dfaa84d80694b97fc8d38c1485bb371cd554deca8930d3461d619e2f306f76998533047520c5152c13119f8283567faaca351eba6a67cf9b18cb9a42a39ffdeccfe814b542dc76a24bbe3ede19ca07d0cbed6e64c8898b235442975dfeca72362eb4b76bf9b4162240d8fd5d7b717ba149dfb3bdd49962486de0acef86a87759a1350dd0addf36c0422767bf451aebb906db5efb7ffcd48db7465ffb20aa7d0d63d8d089bd552a7731b7833363a72cdd815a6efb0ebdb38e85da7f35d9bccec68f48200428bf2238f0f92435e7aabe2a4232fb7cd784891cb74f10e6d002ae0fa923d252d2334332208f8d8ac0a17416e084a1aa8a010429875e83b0b2832de6d609c1631d35e20aa0e434bb8b078d68e01edec196ac07b1697b9c46ab848c85f44b39c2acf766424b332599aa4f5baad7058e425045ff028f68a0e554a131487ae2821379a89a8760fa7358f54b4d57662e52843bf02d414adb534b17d3633baae240733a608da084cc61216f46c66c3891bed872c20babe0e8253a64d78faa3b06f4c05937f3d3dee00fa7f261a0a537873ca4b8ae9383bfcb4e49b066433948635e2cc3cd9f7463d725a147f164b510e317b3225a6d68a735856369e51c76d53654572dc98cebabe3ed2fc5e461c642054455a8e92e71209f353906dac2cd57cabdb3bff5fca2c2661cc940a7244afa6399dd2eca10436911d252e08b88aaacd093da916f452c30a38ec39824fe93d3082afbd3b410cf18843497955b7bb834c6de3f04ed041be5e76abc12d74689d15371cee3720741fd5ff6646fbbc75ec175c31156a36d85da172c01a55528e98e733c3f9c9c38ffa4315fca04865f48c78b97db6bbc42ff3ba082948d3989f7e236e6cbf02ea8a40c33cc4fbe5ea608608ef8ffcd34b85ad62ff7387d72f07854230f95e2fdf89d6b4b3ec768c17a729636bc9f40d5a894b237d53aee6bad8e201abe0cb149af891c4f8a7e8b1004e42d9396c510493af883c07462d09bb47fed433655b68bf9d3e3484f743460d92f30d084842d9948d929adb56d3e1d5008bd23ccf4142e1f4da89c524f5fd8ce891ebe91e01acf075d58f061e50525b929e8d35034e7de5420f5e6111c25b4b7c1a0a5d067ea89743ab345de8da9a8b20a3948f745b7b94430927618a143bcce7bafe36f7c9aab2b33c3485c048a191f43fd162f9f9a178b038b75e263991f0757ea787d2df44ef59a26082d1466c004c28b397494a44a6fcace9405e009a83011e9d3b5ceb4447650a0be77409195f210f39ce58d8b797486bbc92ee432cfddd33c1d1aa5d315afbe32a658fad035fc6c84aabcf6726a806a03c394ac31cb5bbb5205962895ff65f58ff59cffc6f0033dd25b15a3d390dd277ad5f829f52fd5f5d92f0045a50056af715b8cd432ce6a62bd0223bb9c3682a8addea5a0a94ac4c327d3f0b65141e6f8b21d355fb363966c3eb42c1e27cab7fcbc5d376d71e5b58f4b7f1d9450e55495dd632c4cfc3bcfd829e9949100dcd2bf75ba8b21a2bd24506957272650151e8f1bc4775c4f7b4380fd8fcdb92fc181d70b2d238f43a87992c6cec3704b5583f40441132da578200def19d3e9158acc6dd8850897f5b00e139692a4a45237ec669c2bf368fe9192d0ef7587edd430fcac970bbc28a7263d6bc7bb3b89f8336f8ff662e89dc443d4d6a0cbc7168b04c2599143d0cd524f6de094534ed36711e6539cafbfd964e3b1ba7497a875c9d691c03c7ee06d4e78c448f314b2d99b365bfa5f3d21a89dccbc1b5bf00b109dcc46b39b787e7fc4901c96369b85dcfc28d7e7df1268b41a99a0aec3c494d9dd724b1feae012528544ae36d3ffa805be92e3acced1f3de648728b2be1ad4f89a2212c50b3c769eb55650fc7e386c3f34043b33d31524e040f56ccfefbdfaaf3f7a5090c4ed281c434e73dc3f636118f3a606233bb5343339b6dae0a42cda998f9feda0b67924f97fdfa8dc481be8709ed5ce9635c9084160cc11c1aba1d6d223cafaf37c00c8728da6a2c20eb20e6b9b9f0a57956c8564748f213c300b6d911b9f22977ba0b39959b76e213d0c2cd32f850374dc6da730f4926575228ea8adb51af6d2d7c938a417df10950e4d2466813ca42a2959196ed9e45590aa97d09d52591c34e84255dc5da2fb063953881e2f1b2f696a402be2398ab839b3e9e91d6ce0d31da48cb07f8119a0ae2231f11080f90ddefda43e1378fa88f0062700799128efefb38284df43974bd0bf75fefdfc626f841bd3af8610cbdce9f1d0c13ceb7d554c7d8c61287cc28e211534226307b1d8ee8df5912b03c75c3c294d01ffd6b32118c468c40148aa1c8edb23a13b74720d200e31bdb775d76b047f0f779852642fd91dc15c68dc0312ff9d0366a66df1f6a477ec5258c1defb59bf3a369dbac02668679e97266d32f42bee2e5de1b4f1d1947809d4fa28bce048fcc47f483604e466e74e11460539b8425fedf2e7e86222be6ab0bf05c0d7a6f4a185b4a8900cd93029b8300851923624960e0ac5fb97849ceb48cf9ab2c50ac3e9f04cba338a66e51baf5c24041e8c8690d21f688d66cf21d2be759e0672e985a2ba54807213f7384b03218f936682ab094ced688536df88000637f0d58f5519caa3127503be3868aa28105cdefde97bff013a9f7750a13b9d3bfb77dc736f9ff2e9493362bc16b3be8a5b41850911bc1a2d9aeb596152799bdf7743a629c00dceaf2e911891c13615e57d00f5bcb768913b0f0cd028f4ac802144376cc27fa5efe8f5358f417a3e4ef100cf38c31391266af6ccbf791192e4b73c76b0a4c0c29fad0f5aa92a9e4f721934a51be872d34420ac837827905600233140f2bbc560a703aaae8d7ac277037c6bac812b1f38969b97ab84bd47032829f1d5fcb8de72520f77fbb4d643cb0bc450f9f775729a0e44205b89b5090d3a382188fcd1c410873531f62912825a7bdbe17d7255d011a6b45605d188911c5d5801a21206e8397786b2c0b1f5f43c4776356f98caa8e411d4d1722751f1cf6ca164b78fa5a2579f128c24d948b8ecce474f409ea04a589dd31c4b4e01a4c32f3e6aad698fb699080d7fe6597e02cee5e087ebe1e669de1af2c5517d32e00e6d7df7a6ba330889824de1c8e8e1be76f5c56c49bf6743c85a958fbc7c18f37a4cb0472a6321b3830bd790ee84c3ba4b21d904fb12bd70fb4e84e9fba5372b3552b8b96a55cd7a2579a05a775262118348ddd90dcc5e6df324840c09aba3a515818fd83b901f3d44ca6f9e2075cdc2b5247e50fed12a10dc5461bd227542bbab25dfdbed28c394a988b9f46837e9badab5d4ed165b809c72aa0d1764adf4fb8528cdf4939e6e4add9d6f3e3934dd172f0bbdd86c7f2d5f265d2035944b7f530cd42c23a5e131905d24410b48c3305602d9797fddcfa540a6ee5ae280bf79261d350b43d98a883715e09bcb01d08269b802ee4d2318a52f31b77730b1f5e412aae0f818852012c6c51a0cba3f4da0dad7196ccee02846bbe417511aa99be7311f14600f04f6c60131552530e211d5821d38ad95fb4cf874916f3d58a7d65513cfebba768bd458db954b8228ae345790f02022fb74eb8f462547159831f4cd28237e0c6a482259ea772f81da4d551998f058177eb0f6deed2783bb6c2caaf4bba6072c52cc264442e2c511ba26f3ee888eeb16ae4558f0cf67edddf36f8d3747145a5526fb7d1995914cfa834b1803156ef34adc2a3da956ac26388d1c9ee63be581a57385c653bddb2f3e5f6af0b65a3e1dde7a1a244cf432b6b3c4567f96d4681456a445d1b696df6582d6903cd87698456dc2fd5a8e602221e84f0320cdbdda4421f1537cb12dfd8c295c86e4e080c0298302918af5469590dc67d255199d275d363e91728fee78d80d2f489fe7664da943f67490309ca34116b717d110594539b5cc4b3fb2adf16fe2779e819f9976feef7f7d27eb012f02d209124bc4a197ebc2904fb950b216033d5663de19949e4b1da9b175342ffd9be430268dc3962d6c4fc148a058c82f6b48d94d57174a010474330b12bb8c444be63a0e145ca42863862799dbb8228f0187812d54337e1c82a7b79c31372adeb98bf092b9d6ba3fab1b0336327b82f4ab6c39b1e7290aaa92ea0e5f3e873193c6e693bc0a8f33010892f0cee45996587ebfc7e0b609eaa3218237004e44c23f3329aebaaeb7f58e1ba3d4517f38d32a87a628c0f20577236759c638f11e271b962d2246762866893bc61148cf8bc6351fd3afb251c861876a1e9ed21323e51a2bcd3e58ced15da59bfa279dc628b45cde17aa1326619369ffc671820f538ba45e0449c7541a88edc61e7f238875163505966d9da161e241da553c3d40bf47bdf6107575619cb308156e9e88644b1dbfb0180f50ae1094981cdd9502006ae33de16446fa64f22594ba13ab0660caf497a4c3fa84bb897ff376ad1f8b04ca66cbed2e397fe9ffa51329cf8a02ce6c5de7479a3e089a2bcb624e8f4ed6f1714d4c470e420be2213683fe49f52c0d8cce834b73cba4b5002c9d0b962961bbea3ffa0f79481a9d546e53fe22541742ccc7322faf0e9e24fd8c9e814b3ca72d60bf55d849422f9f5dc3351923ce89f076b182144ae89c609ca2967f8010652bcbd397ef23b001966f0ea4dad141750a939372a7f48ff2ceee6469825721f15e591b869a53d43fa93b126aa5078600e80e712eac978eeb3b9b4f7f3612222e647882393a48dea0f8ba6a84d84ec2e0308a9589096a6e2f2bab6a8c6307496fef0993f3283002abdc39cc7cc12859ca5b6fd25d1b2d65c0a902e8f23981bbb8e62aef47ca55213b390b0486680c1000ac2aa27671ccd74d4f88a8a33d4151ea22692c1e88437fb351cb60c578de5c7c45b5c8d8ed2232e8f1ff6055be21e061d397d61063d8c6a09faa6bbcd1515d4485dfb6b6a4116b61b2aa092e42fa2aa778380e76096f143c8c9cf8e5e2643311694846d993fae3e2de71956fa45eb48aa55f4b8089302bfff33e4c9edfefdc02c423caf81838de1ea444a8c51c366081e6da1d4eda67d7665c8775eacf731cbe0c5b9da19d93d0010ef9e19e88360744281734a9791bb2ff255e4f87777e66de035e0bd213f73c79d801a9433a7defde1a5319555349fc680f93abd46ae8b7583e43c02583cd30d52fa31d974e1ab4d57f51c838262021a5ee71c73de0b6df59ee34a0fe9bdca5ef9a39918b9259cfab2513e5e61a89284b443859042adb1232a26bc28e6cf553942eefdc3afb039d20b367b0d8d0386d0c5878da5f9393c20002b47b5b96556219b312c5f4886d12e507b0320f9436d22b2c7eee4ffa10c696045990177e5475877d17b8ccddbbd36fb17d3e625d7e66c952191587f8addcbd58523a63431096fc680c1cc4403ac51f266121371668cef35be8f6e3b2bc93b3755b2683ad044ffa51b0d73ded38ad37abfdb1a1925ef4a24820bc46d3b7b0ad492f9e776894ed428eef16cddcc5ab434156b3e2a12f985311abae7f5889fec3fb26f00808e822864e8a2661c15028f134f9c2592c597ab603b51d05b4f70eb6ac6e7757fe366dd8fbade111ddb52a75ef88678494085d63784c06378a45adeddb96e0802b37ea29cd8ebd4a1e14846584d762d4190e860087ab6072d49a62e3da30d2944995b2c2d535cfa7a8733e3d2c6a2af5fd5263ac524924230ef4aeb3ab22fbe29f832ac5edae38fe09730f3a2d82190eab3013ff6a6a06ed45bf7e50758f95a2f92b7a4eefd112e9ed0ec69bceeb07831d76a09ec099c22a52b693d027f6eeb2ac06cec7ca03255f58dce0a158c46fe4b1f2ad5879c6a441646361817c0151f26a81c3244a33576261e7aaca585a80751386144772667a7e345827f66bd2ffd3f7fc855d9ac7bf15380f5df155b2beb0aa97c7417523333e93ba93377042831d752901798f1a0c3fc093249c378c7cf7301818a2eeceeb19aab050d97fad190385a8b025018c44773af7a3c4dafbd8a90c2eabf64f17a47495ad302ec0d13d86ae460a039f59e470b0624c6ee8b55b2595deef3f7419b9e62d7e9f5c4eb8e4c58d4b3327490258925f73488ce47e2a26120a4924c751939eb3713fb969c5ec74fc5c39d50b509dbb9aa2a9d8e196011c86808df800fa53652812d206f8a8124c4b589cc0e8be7c9e2ad03f5ee9c88d9f76eb48b770c7726015f64aa425d811518496a5be18af2e57572a6b311ddceaf98f3aabdf8e69bc36d9277b35ef2b95af30161dfd4713b5e78e381c24d3267e2c98d0d3658142ff423c3300b070304dbbc9cdc6f0510cc59676f052329455f41621643ad31cd3a4ed4436ae79963603627241c74cdd3764b54ddee3f9771059c682ac32d1ed7053aa89df5304ca4b1870b427073499c9cb105359168db1439b23e1d9ba983365932a4724fb5ae9b01920c058dca134430ca3363aaf1f4dd751f107b356bed6492413f23e48149073a9d29063a42029e7f13411e912ec52d3f7478924fd6912936e47fdbe93f7c735a599e3723f8fcfc23fbd4e9f6f7fa505e8ae3ff71b58ec98851a99ced9935d8d4d0b26d9dd5e6d2cafba4f0a4c253d064a334c0ac84a114245b71771ce299fe382c41c8867598fad23345fe60168ee37572c16747b10d35bfecab0ee771d0bd45d99e5c616a001059aa940c7b107e5f70fd5b6b024dc38e5d7cb3849673e50b1cbcef86fe92b11a974e77a7779a030c5b8097ea8ccd47babf75477152381043239fb4146de34d913724f320d54c1c6ca079e4d95e75a9de8c37a68c567bcad8a4d5becb9a4be85c4f105d1e9e85e9d83410ed9fdb2fac6b27778695c21adba2775656a882a4d07e2a180e1f23e54ca6dfa7c3dbb3ba34e0751590df67e84850df7f9ee12272a0e4a66b2f990fbc522cde320bf02d69efcf04afa07fdffa9ef10e05621ec175f9b33112db8cd8739218e923c1bf5f59a0d331276034f54cd151d486c55ced768417d2739b554eb116ac4a3d472d06789f2c582d1a87d4f9394ae7fba01a281e371c9e1814a69c78fe7c7e045a9357d13e5af4b61fd837b75eafed2b9cb596396baa91b6cb3517b6f52397185a3511a64ae5547e00b47d6088c4ae8fa708a80fa7de99ad5c4872253e3e9f430486917218777059b10214a34ba3c0d1e764a3a2c70a4bf10c4b29baddb4ce9ddf1c096b77ab7bd3503c13d2a18c39097c5fa6baa2f3c642ebae0f6dc8cb807627d13bdae189ab8d60a11d43654b811e2e0cb5ae8e0da0971eaf31e7c95d097c9a99c19543a5d8c9a39faa2524b0a8585ad9e3f456bdc31fda728b3813da4e4b99b69e94785f20c638aa6f772ff0d89639e3bb05787d05019bd011a572f5b0299d273c87791147cb200bfffe4ae7bed32217b09e196740746319eaa55d84f39489dc15d338315c143e3200494f68d5a845c42517499c2a654c7f5c6aea8155e407248981f02db074a63271b6a64533e7484bb47c0268b72ae71e02f35b97fdc0bb86c5e126a2915d63f0c3ca355239dfff2ec0c3812f8e4a4c670d865b844f9698b330cbd3df65abac9dad886e61ba42fcd7455f1829b9fd06a61e53cc67c616a81ac0272e442282b11bbc9b0c030830388ac4c06528b594241a0876a9d47c497a288e7919036756a59289f552fef63a6c5220d8ef442f57d2c09bcfe62f7bfc806523ac439b0c31fa6a3795d171bd8673908191d1e89b5aec718abdae42c38faac2aaf62e1f10c30f1226f7ecb39d31adbf9a10620d2c1ab226d45ee51c79d19ed90deb66fde8cbd19200bdeb9bb2b4c704d8cec78dcbfd92cfc8330a10decdbe0c1883785667cdae078e6f8678a516ef9f1f680b966c1f9fe70a7f3ee912c6fb3c355296f7660594bdfe8dc8ba1b4cfb5f621a0e9e6ebd4739c6c4c5adabadc26d288d7498055d21bd174f82415357fdb310556545cc4b8dbcefc994391083b5e8094d91b65c7da9f182408da074638fa1511b069f8ef258b634d56cd98511d84c7901792a464e11780d71c0d9d562d5ae508464a1e380c4b8ee08cf81d929e9124208185557921ac66f4aea9718403da1e7d6cca072b408e22fa96da7fab2ead6d1935de9525c2f5ab136d60885844a1d6bc9b59cfaa56af564f2bc1ed20e3a2d512cac0ac9adff07a6272e8eb3b5643cddf8c3052110aee0a0023c23f1778f2926c243c899845c1b84e062c1fc1e472958ee146829334d980f5bfba7f90770b55f842a02ba9ec864d4222d212f1326a05d0d4fcb018ec477b83e88ff7c3d87602e40b8d7b0abf1956cb60bfe00600a57027940dedd7567278614bf8c6c4831f851562c4c3fc0523507687e3862231fb227c3bd9666b9f6c7323882f891390b700aa016e0d15e5458cf060081e0089c68bf91c5520ce3aac11368184f7ddf9887d5bf9877e4ec86dddc84213c3754b8aebdfa2cb5593b915fe62cabe7a825f8f6c9ab8db7ec8286b2149b15a5798c901c18553dcf09bfe59a3944d05a6bd34f2a6e1e2456603aab1367fa54748c06ff9baf6f846acff2a4a23ee63dd62d560d6ed2baaa95398609b5de847717d0b3221c9e8666fbdf0672610da7e11b299dcb50aeeb5e8a19edda25302799a4a138592f3a8c1a0914032bec12ab5a8c70ca0b782c7a2ed0b7dbbc3783a35fa14ed4e40fa366bf47d194986e5f3601a3f7146db52115a2d0cb42cc66f7da7efc07d93aa484e756c823ec9e89bbc128e3ed407ff5dcabe61351b40daf4509089d9d083fbcdbe83a138520fd57a56df3e86da54bfe215153f4c421c08503b2aaa5d230dd16a37bcdd33bf7c1afbb03ffc0e824c2744fa868ca454f2bf74f3abd21e4a9f9e8b420f187295c98425fed56f0caed18987184ac6607d9c9c01797783da3795eb906274897d9e6aa6ae98cf70c194b466d843f113a7f922f682775a02474034b90f6cbbfc37325374734da088de109fbc965ff251e207e553658c29de9f51cf03b101804085ba00114a5fc2f99eb48780102e170063f532983ada619f3fdb63b9cb6505f5236b2665da2aade79b27248642d3440f84068e51c718db08044c7c7d019683035dbcd54e2ca262f0ec3ae13ee3b48853579d3e15649eee88f6ad2fefc0c1fd1b6485f7c89145996595ba20d8568d65567f69335d2d0d7203a7c94a4cd0a26cb9e972073d8eca637c309a1ec3dd1ce4b295454700df47866baf893fa76c9a3c63c1660fd4031a3b36c8b0d2dc7b87b17ae54407bab9fd09591ca0345342622924298d7a36ad5751be84afe865d8819b3ff3850d564864a6986bbaf8c648ef336922939e37e5d15478fbbbbe7337e6ceaab763466a215ac56df91741207a12ad5599ae4036353b1c518d831ce35f585d4bfcc1fcbe5322750f8dbe7617057f45067422ff19b30b3005fb777b37aa7fd35b74bb1abb5a42a4216bb69bb99f6a93326f650337336f031512705a617cb2e360a715ce1e2dcad47f8bba12de18db12ee5da649a40718215eb11fad0f1802c66ce589b4f12f7de93b7385615e85cc6404362600a5fe9d501d45ab97ffde95e3b1bb9564a09c4e27a5456444cb0497f5e2e4e183fd43e0599faf4bfd813d06b0f1fd07e780942f7216826d5280c4675d6dc8e2f32709802e07bfe4912b637959166350fb783ac63c21d5253801be5075efdce12cc999d1026a06196fcd61ec377e6fb7a4119d9e0bbcfe87a2f650e056d5bcbfe1b731b50a8db2add9ebc80ddc59b5aa88cdee4b1c8923260ecb3252457da1c04931ff268742fa7fdfc773e1fbd7cf566ab36ad526dc5f68d746898ce3b9beb28e1852a4a5fc59648fa049c3f01e57ae02833bd1adedbf2c1c22fc4bf60e2546819d98ce11b2e7aac4f949d20d8d03943377d46284f55ad293d884d6d612855c09e7a560bc1627345a00357a279c463bd2a6f752cdca09b940d3d3bdadbc9d560bf39bd77b070ff015b0752910b21a7d821725df9433207da19f290b95f2c8b11d3c82337d4fbdc54b64d99b7f5f0bd4bfa95fb21aaef988a8eb37a8ec430f3606593bc723d8ebf1607082d86c22fc2291cc73d1a6fc92e43f278ead1184464a175e0978ca30f7a515d5a33e2f5e2bc4cf6f499465be95548ff10ccecbf36af8d614a03e0ee2dd331c7d76fcc06f749af154e98b14d2655e517b06543a061a44dc7f12635c9dcfa21586a0e2235db83f2cbc0ed7981eefa75155875fc595e83600b45a310743412175a7f8d2fca6b5ab65b41bcec7700e09826237e01e37134330f1efd4daa86d3460f70358307fe8b43182555aff8e4ae59f8437dc44c098aa3ed073b6daa186cc6eccf764e87684e8a60d79461a78856c66a7771bdd256848ac89fa302e456334fa8c2e5901ed66a632fc82ddc4d05f508a63e59eda39263d864a080c5ca2a49c51b2e324e898e844b07bff0a19bb7463415fe81b58e2263b17c86f4775074d28333a85dc28733ac4ed4e921e200a9fb7e14692cd3174debc2d03fd1e94c840a6c028fa1f1b79e014228b73d10d7ff42a3d7a4d7bc7a380ac21dc418ed2a281df4b0a59893eee62d26a0b8d22dcf70dd7b62ad09180c519302f2e395ec94a7724e6027fc67fa888f9811bf9faaa35c42293276bb07dd8c9f2adbd4ce123fdc9f9651a7c7c6e86f426dc0f309cd5956362572dec7af881f329ac604f6a255bd8da81c95c659d3885c4aaf6f85f9e46bbcd2851c28001dc51a9309d2adc4157d4c9541b389f578397aa351737ecb52106924016ea57b2bade0d53f6cdbb121e6aab9a12e56b1afb2daad482a41f2984a5f342b89e30c77be556dc696450ef56a17db7a32dc9d8ec51f9c0c873495e11b1176a22a9c6fc144cb352b93414dcbe6517e3fd1cb64b4b2c5625a96427f52394b1305df1168a59c0ef00ac7971e7b9d9e33ebdb05c85cf6a13603ef7c6ccbcfd9d939e3737d565cad15f141c2a6b5bbc3b939c80adf127a6c9bfd82259c4de6dbc20129b589c24db0c7e8379167569db1f3a9fc2be2d4de97b5f355874e272ebb941cbb8a61b798f92e68d123ad272f22d8e15b4c1d754d0761ebc32e4a6ca99d5ff902aff6400b4a8eadeaa8d0273f2197b5c0fa4c808b1f78c0d7695ae90d08bbd6282fc02871ef9b4b5c5cf6ccc51ac19bc85d18f35b7c89a809a2d5b9441ed25bbeab1a7582d2cee55b7cd33b4a6b4c98dd990d4c301b05c7ae8cfa2adc3eae8c51a79fc30aab8eb4cd58c83d65601fdb0920b1f2fed244af80e675ec9c2f71f0807a0f20d034e9f375a0bd3565145d129f0937964e8a94f0cc20419218f6eb2e6c37f444cfa4af62f7646dbf9501e3a6772970e951a90b6ed86cc01962794dbfee14dfe54b935252a286ba1f0b4d34f261e8408a115a3c5a17db1eb3a1e62cde9e9f832197ffcabb7bc4a99cd341944e82dd4d1ecb3d5d93956e1ed3e795fb8b989ca1757c7133aeb7df643de1cecadffc943904df3a10374aaafda3cc5e4f22694020eac62be84ad1b6a9d6bb567d867e1b08e917a695e1e1d05765fa7ea93ee1b1c138a6fd21d00714698011a2060c81681d4eb4a9e0a2bd17dc005f07ddd83b3b58cd6323a5d39efe2b6af38cbdc9c0de91de7a562b525c968c8d6c44af1e724ba312778415f9b9c366272abac0c0875d6bde12437b64f77b12f2cc9a90ab2065d6d72bcebe8d62c60241b51d2bc1be62a2c1877621c70bd1f78c42e7a2122883b04635fdf4124765cd83c6403733ffc60effc8af1ee6a1cad1f155039f9c28bc580ddda4df57c67f535fa646aad5430a96661da041a17affc9f1a6afd7bae4364ca2115404fc25928f0ef9f649fd756c9381f07b98bd1718dfb342a14ab36654c3fefef72a187117f1f174e2bc25a7637bf5c7e302bc7531b528ae448e1bc5d4fdf029f563491aff301594716a55077fa5de411364098c72c24775dbcbcc3f48bdfc85f0e82e297e879e44a7ccf7d64c622fe3f1c2f3d9f5c2766dbfea570403f01175fd7646b7385a873e09a09b34517a48f5389c36df13afe45b21956dd189b5c7736c08fa4b43536fa14610e56d78c578b61f54d62e670b9c318ea5c061abd86fd9e60fe3d9cef5f960cbf2ba4722f1a21e5d72d31c147d3852020a45a237b1f3fb91714b1f5bcf30bc3dec28f2df26083d7a552553897a595f6814e3d706f40c851866ad4e794a144008891c0792bbcf3883331d3774ae6d8d4080269e3a26f5e6f9f0c0a243c34c98fa42a33ef80db2d5dd6aea1276f1baf9690974f0048e5d7d910bb1dc3e0f9d99df20933282aaab65eeb5d45e630d9629ef7cb267abf90b82844fb42fbfab78f8242fa86ca88fd12ccd200d264730b5af67b52b18e467780e61a2992261544f2d043b24d6fa8f8ec912e96596f554a6975f2d9830c2d37a6ac221d3bd379a7d4191b641db3cdcb1e789d36eca9a578bbfb1dbd21ac21a742faa013cf185df479978606c3ae7474a9c03af78553fa2d850eee41a2c7d52805c144ecda49415ff78e7e950176ab69854d266bf076992108c99ac61d0ee138cdb4c7659c0279311097b98566a453c0cc7261205185e14441cc1193d37d4f9bd1e063e6f1a437b0968d5210728e56f8ac9d42fc3082cd25758985a47feaa477a1f94ad46cc1c215c44611361a1571289e2b38101f92aae2229f52fb05904a8b1d424d7e75b14e309f422156d0f4bd0b0dbf349285022e464649e4fd4ee75d6bbea0e1498774528ca940f5def41d21adddac7255e832bc87a2a6cf4f73eae1ae517b5ecd1f24a00abf041d78467f0ca0eec2515dd5d36c56cf1fd96e381de9fc69c5f048ef32eb735feb90a7a2b5ee70f555e9c8227eff7c9f526d5f1c3c1fe2ef2a0b116002f82e2bde077edc162e07df380b8d516d8299b46e20c254de2c2c30911a067db9eafefec38670f6fd6d020481a8634bcbda72ca7ebb91dc884c80a9887a8e48d04ab7ddc81d9fdfe10ca4fe00596460d62bb5022549ff7e8b4775f6567bd8e9814d629719a021df907001633e44606b1ec2e4b634647d3943bade9e2fb8b1b77f5b8d6fcdfac50e4c0825785e883b56addb6bb5d319a4e8a50a2180b8819e718b6407d8e5658ff1885830aa051b440e6a69bd11362c1f6af5c4b4ce305142552848c1b91ae011001e5981086fa67acde23ff7cee0b12d0bd6350618be7a15d844a2f2ab92ab659eba44405c3efcaa835716d8705914a42f9d1d88bed6741cb179c4dc46c1f1055c38597d40ee4c971e16ee094a363961b2828b3ffeb0d1a7df9e7e81e9347094127396c982e28f165b05f2c578f1d7b1129c2e1401018f212a7f130ceaf10d92c4516aaac3c1b961f4f5348af3ef571ea7167047bf3e8bc9c9e25983b23e2774d52fc56d85a3f131977d03ea878974b4c9afbdf94910d310623222c061458d4c8e96f1f25cebd320a10ad5f567997f5e2c1decd113dc58334529293efcd9936513bfc467b35d84473c009f670f1fc5c06591b161daf1966f70961c01022c417bcb2796298941e7cf7fafca2f197a67127ee21c3246c2cbdc74ba27bfb6ec2f03b4637ebcbebe0f78767648e6ae18f3ab69f8f373cec1a6ce28af937e67b34ed49296ccfe57256abe4f657bace6e79850da967d151038f2c5eb81393198c74297a8a56e9995edf5a4828ad6e0d80b75433c44b14db8298bcb083a261914bf6c994742d61d52dd327fcabdf6199442f40870fe0f357adc9f6cc6cddecc7d17e580af21a2be1a0516a92553f04abacf0674735a5bda5d8e8d34cef248d824f2b0fd7f7e94b246e1322ead9ad20b7533361eb16119915a6b7b3566d96b74516055744218f0cb392a70fdadccae6df26e197ea4289e649b4962e892195b553d8bdf0467b403db9e2d906dafa287fc54575cd69a1e2a435833abc424508525e860cf289458905d8109da4ec5549af069e1ab731ed538f1adea1d7d88015d2dff84c5d142fe1dc1f4f35552247bd96f6e9d847386e248452fe8be0df3c9893fee6f91292b8f4eab9641594555be707106ad8a098653ae8623d6951495093f17bc46e5a2e594796f226f70dea5153e46c3696926803d5b1e84eaebdabaf6f10edd812fd107d3cf82249cea5ec6707068f1a196d828d6f77c5ebfb8a5a353405eaf8e5d0e033acdfb1ecb8a49781b2896c3915f6e9bb8cd1d612e64e933dd86b65af373397d9e702193fb2a2b575797bf78530548fea8014ae0b46888ba97a781aef7a1443029ee440476e868276f5707cf4bf5fef806443f7279089fbc55ecc16346d010def57a2eb852bc7865f5bed3e3315e6b5c12225ff84c7482a36f6e38b8d42272e2895e9efc0b0bf7e08f94870bdccd5bded18543a0d8eeff51193bb9efcf3b6ef4fb639fac42d8c4d97d617383df0445b9a7bfc0440b3077bd7d0021aa43318b40bf020afa74effe042f22add702bce48323c62cacfe7f9c1c88ae9377b22ddd53a6b15950bec197ce3f56daaf5f3cabae6fadbc71e5eedae8faf038a0d6e0364b785c522ba5a8ec2fd8717c5bf481ac3f1c08c436639956c6b5372deb67d1b618b1490228426e7253a062a353ac78e2eb8d8660ea98c432a1109ea3d9521850edc8b22a98df46f054212c18f8a5212e994545f0d666d966a8d102d09e9df9324e86f9bb46d95040c3c145a232ef4e7139fc97e107872f6021c1ef317b9e33ced3cbd14a1f5aba232319d1faab0c6919143992aa769dfd56a4aea224d8b51f44038ad67670ed239558c494fdba0a98d83901ad9fbfb39713c82b2a9f168aa1728d35a221307c024bc313e38fcdb77ee326d22124b8d46dda71729aee776b9e6b0facad65a7b8b89b7c5866ad4ee1d93140d57b361b9394dc61f31c7432559dad1e60987e838744c300ba5afd13b21d7ed078d6a12a5b99b97e961cb654b6b7125ed8d79d50905618e4a56226a9fcd8f50fc9c4d6fe6171a33b2b0bf3f3878f56c8dcfe6b37cc38e91aa338a93d2372c20422aa85609c44a9c4e1f966bda8a405a3a3c301fd63a52aa6ed34d13efd5f915b1eae95fe0744914b23ecb8893a292020124a7474328cd98e8618679729664249aa4faa562c72b520ec23ac331aee037221ca5819f1aa97949916e31f4ede501167c46711e3a81b314a4cfea467e37275d3ab14b717761171b556c110df4b7c53733786728c40f6f1fac994fa3fea501fcae2ad1d3b0e9c388ec2df0584c72f6afaba6c9ba849b4438a8807847ea4817a85f92d6d20e5b4d53f1d00682b25db95916c1fa715ba61b2bfc028c2410aedce79934525040ace026b5a3d15c809841484acbc446dc312eb225db22f0a34545f7e4ca5c93b0bf6632ba9d9d2fb91949f7cd21893024966871f7d1afb66e9ddc29685f58684c8747d7918022365338b3a4d3c0c8d6f08db31a5b3d0e1ecf6812a6b3ea76ad58426e1aa0e0b46143cd2effc28b4426dcc5bb057f8c30d4fcf5f5855f1ecbe0e9b2cfa6472d267baa84cafbd37a3e44c70b19b7a1f7427bb32dce9883bd5c7ec37177b19f93d57522ac9f28d654f780956830ca10c1fe516424ffa593a501e4ceabc49e38cf6c71059da94d433d85953300515b00547bb0ce60439c908806c339549f15b1096d2dae582b79d342dbf0652d1805a46064ee295536325e57f9144323d1cb3e42192c25587da2cf0c06c4db6090acddf02d48ee36e78038a2c7c58f45f803cfb5cc1937dfb4d4e1486528c86f0df69597740dc4faef6538b584f6cd873b167496fdf643b63c9c53a0db50e345750faa4b24290c95682e2df1b031cec8584c320e9a70d2f475f802a8cefa648fcb74109b32653039d15ce923fffc8e127c2247e113002247133b1dec3cd1112edd53cd364c200f50438a7520e257ae37d2bf53f6d86bc50da41245457ee5f14a784b5e05dace0bdb476882ffe2954bbebad08447be7c9872937812bad9dca2f491e17aac999dbaa9470174ddcbc6ebd0d9f04e42a6b3b2a9ad9722aeae074fb5814dd6828c1984f906f5799734ed46c74c764bfc06bfb825b12b6ea267defb9d7d89e549b70733d65e6d9434e0187df4ecd9a093a440fb012dcd25d0819568287f1c417e9a933a29df536cc4d4284474ca3dc70b63bfaeb2258fcc695e84f62ff66c726a9de4321c8e6dfa1eda31a988b63a171c3cdeab4ca12c2eb5ccd09bbd701d00148c4413da4ad2d4b6c15eb8f76d5fcc4d2e6596a2e90ae17cf266ce194b94b404ca043b00d92cbae3d2c9b09be51ef957040d8400b06031b640c5f7860b6e6db525547428c41694a346a12659b2b5c3131fa7ce816ad3a8f3fbb9968650d705485961cf87e6da739bab64c370d9094f287028297e68fbd769504cdf9d3b4900ad83e4a597758ed2bd41039d074f7d7fa8b5cfb8e4b2bc5100ab141b0f6c792c2fe0e3e1b651801047b686b15b6b413cb54d931ec893c77d1abb73887f5860d3a3e25918b14ca4f40227e78f5a38c09751fc02667869e0202a9ca0f0723ed165487e60ce5b4d1f52df8c786866b1a92c6b38e68e683919c94531bd2808af3ff42cdd9c66de978aa31c2f788272325f8c099c38632c936add398dde31aeb640534e8563e0f391b1cac77b42506aba579d609c4bbb1f42732539e1489252efdef4dd1e80f03dd81de7ff24ee53544b80b5e3e1fec74bd76ad97229ffcc66a7e2546dc6293c2abff6ed3a1ee151f994d629ac56f714a5bca1e6972fc98891592943426a19b3741b9f3d083eee879454f649aa90cd85088ac34891ad2a966eb281230b293eb65d45cfee5c471155dc0b552ae5a132f565f234730d200c95e67b73c0df6d67ffbac0c59b2961224a954c78ccefaf88b41ecba86b6a95f135150f1a2b8f9262da5926b9816e135ddbc42eb69cc1874b759681e17e56fb829df7644cc4172c659ab143db6f26a190a5501802d9259c9b26e17a5760a3bc8c28efce04f970c74a7b9822cea0f3965460cdf5116d7690c0a39a49089ace04eb5f87bb8c846200a6e9dbce0edc3942e9885b45ab89ee3d91ad0ce5d04fdf486f0441e3a87eeb6d0c734d6306af0e07f5d71217b9cf3292977bebccd3fd348f8575bd4f255b2ac4023855516c9cd284305e1282084cdbe21c3d73844a072a8df997358f9a7013ace37195c4505dacec54d7bbacfb1414a33f34977cf08d9bbfcfb5a2586279daed15e52c2255d5b41e4ae617dbda692958ed0cdacb58bafe149eeefccbbf9ba01aa00eba8b2d3cce0519cde3b177a3a4d2c3d52c8db4a6b9c443db342f74f329c4a51e5bab3f0d5c64f422720ecc1a9e3c8709f1266739ec6c4202f2757b41df9c565a5ba20191c5c249f89ec60c77755c6102ecdb10f927873c858fbd34ca190580ca5d9098d36b457e9cfccb0def40a22f1463ccf02a816a68fa370f7b7c133b7230f569030d0fa2a9ec349b6ed899ddcdef8475cc02369ee62f2f03228503462a146a06cdc61660bd343551759511b221973d5f29d2318ead191fd150ac9f19e8c67643c5aeff77a3b3877cdda874cc1d2069bddfee485ae9e0d7dd22d5a920b0d5c8af3c0d5fdc45c437220912266fd4330cc021172f94421da9ac4021ee5a1ecccb5dd1a6ae63e36033b0a307e7e08dec2bad7d2635fbc3c17b58ec2f9248a4e43347d523cb9a617f6a8d2c076de41e065ad651da050728da9285a63b5f97d7d01fdebda0198958cbcce3d6615f50530a6a82a8b4e3e76d6775f1ef7441d8e59316e9a9291f45917903d26f8a67071d4bc3b17b8d58ee4d559113e3aa6b5e7e6f9db9a492afc31f0da2fadda10c0d026a867bfc2da1be5855f43867c56e3de9e8d8759c5d90a87f538b41bd42d11315f73abbf33342f634559337142026e5f573e157d5aaa3c79c10225ef62e7d8d5b206ecb94cf29d9fe16e2a817ccd2e43f0fbd1de0c059184c2f0669f4b046dcc9fc35a958d2a9fb065372cc24320bbc766b7675053554dda08c78f043a94f0ad51082dab4ab4ae8ae97a5551e182636f4e46c55490b6ce714bf5eac418ca906a8bf66cd729cc5ce5734da32e6c9a3c467d96d91ef7f76e83cecde45d88c6c0924c84474780b14883715e9bfa241c61688dba51ef405227e300d4051af5deafb600cbc2b8630799e100419a37bcdc23c5da122413c5a1e68dfda22fc26a12d61e3b3426748b592e81d9a08bcee2263e98390e42899f8d170ae4f447d0c513157f71a811bb9b6331285e9388d7d8d4a5447960197f3d6ee555d4e0ac12f96d932da0e3c3913ee7cc861c2b4a06dd70e212903242a23a51993e0a7bbb31cc952c7667cf2c9d651d4bc5b238fef90aa19f7b0e32f93200073bfce580405b97edadb5fe3592b339ed679390f470a28ffed70928e0d77ffd94905d04ee263102d473b70c5b45741a9c21ae308a0afb735aee50e018258ba43d66f7ca381e003730ac74dc9f97390dacd21728b51bb81439774cb2694780417e2f8fa4051fffce9460c3d1ae7e6eeec32a1d0772a66b75a7c40c51f21872b2bc824b2698326077f15f7c5a1e56bc06526c6bc6f41f4b28a1f2ccbf269bb5bd74bb9db8ae1409e4bf49c2a2952a0e9e45968d68411f0913a01595bf8bc0ca48b7fe0c014018a109502424ad43f446c55bd28324fbfce4282fe05886b872d606fc0688e20ce8b2602a1f2516eaab4dfbdf42beedfa005df91d8b9c24542857775f2628c232226cc725eaa88f948b12b34c5586039b106335d6c7dcbf28d1617373500abfda5c4217d45510d5d5e039ee1bd140562c42a66b24e5026f4c5c97b4c3d606731c4da47b5a452617720db5591d40c1d600dceccd11f91d6b1fbc2cb68ac58858541ae6520aa0b7de6f5982c3dae7a565d60617ed3b85b8d9d8af3b4244807f7e253e8c54088070e4f782aa7699a2fda5c4167a372a7d50d63e0c189b4bee080d7faf4168490fd05c00bd28920129cba1b5663f4768cf6829442f033a089b122cbf832debdcd02c8f1716c8ad5445993e987f9ba7e6e338efc85c2501982533147b11088369a94cd9791d7da41cc15d30f31f9d220fa62c3672d2caee3c918eb9edb34cb2327de88257e3021c67ff93d675334b51c13edfdec6bcf168c403b970fa4770220d617fee6151e625e9663fa3ac726a973c794cee6e15e4ac7420807605ceb679daf59d983bd295f632a078fd9a8e69c61e9a9d69edc7dc7cc521037b7d9333f55e65d87f0b916a66fe9e294f26e0864a1acef3a4715c537afea64e2b41195312740bfac3791f20a51b609abaa1d9a90f4494c59b65407aa5c42c58ce8e4053e4b8f49a7c8e9b5d25ea5da7871d28bc2f260a1bea10a7723327f2caff6d878a8e753ecc9640f9cc1102532e90af93d623ebd84c81c22fbb7fcd15e35e9a9e022346042d69c807004e63fa1047d303d56c332ead44c1518e6c1299150e222692516e0eb3ef8e94e7c15654a549166bca1176d69fcd34deb445d590d1e495ab44e2d1c68e7d393673d20c7b5d468c1a7a07bd26a439db6830d5e3014026e16367f6517f5a442cc4911226a7d00e8886f8a63202f249743273f430777478beb049b638e143c17efb832d83da273c1f83875dcf21520198796c50a2fe2c2b6eba6f88d9f152b2d24d5b7780b9bceeba579b743a4f4ca60326114aafb6795bc07c699eabae151c34d8508c49cace2e24ab3691f8353a781e706a9913a2e693b1467f82041a55ef68d2c85a00704662da2e72fab6e626cac93122f58590f7daa337f741c1c732397b8292bbc51d9d16346e6fdb34e1525af888eea70eabfe2787bcd1afd45f498d16911ed4836f44bcfc9e5b7d84b8b16d1d9a90b2ef04da502f4f3608f15727371cb100052eac15d4f2c89c8948397e87445512f10fb370a412500308351947ef6dac31aa36af42618fa71a68adc12c3be9afd7b1765320001119b921b3bf838de51089fd2cc9aafd86dcf0da42c2957e8066cd8657dcbd836f7969e543aa5e268d3b70cfdb5857ae721490c7748794007d1767566831695e78074138a1dc90bbcdb33c9a23faf3625943cce4ba94c37e58b4c0185f6c89e89c47f3035d1f1dc732b914097bc102d40070291589a25925525b59366ba8dbc71548812b8551039004ce72e8ea9714e946c984035dc162306f7d2a7e2023291d1d2b2e96c178c9bbcd23a33c894589ac93ae59afab94eecc98afb630d6b5c893fbaa25cd24f384dd2a6a56356977ffbdaa6dd9d8a9db82fc0fc2102272f31806335dfb445836bf5b6a4c7f4f3770e3e41881da180bef3d6cb74f1f1f14da0f1379a069d5bb6b9ede002e63dadff71c5ea541b37c5405e9e1773d027fabec60f9214baac711d5d988ebd6f891da871d5a09c5e58b7ee37ec2890276d6de2925bd6f6c12de4156a0438336a70f7a4a912c6f8a66da4d3fe29f6e390636c8c80d2aa0f5c16ebc7c153187e16c5b427534946a63542ce289781c5de6615cd207d497492de8dd6c74f712d887212d2c49317e85e71bd332aa60bff813023662c9b9ee479c501d1ad01ff58ce2f27b06a68662c0a42b2f09965a3e4897ca4d7ad81f1e7601e1d935f615075542f7b14075fc8b328c5845f1ccdfca991e5f52782ded62cd9619694b498367253adb961879b8f98b83852461a1c1cc04ee14845c56dd0b8821b2ded7d753fa5389e8b5c91d2fe91f4ffc768ba17a1f6b1ddbe9c25603dc840a00523dcaa1be18742d24ac89ca3bde1abdda56970f48d5765a339bbc347c97e67d322c8512491fe4dc0e9f6dcc2142e0a84182e151d1afde6fe99d61f3cd34cd50d43ecb6e79ae9a90087fe502642fe9b42c046871a204409bbb03972df4b6ba1167f19ef2120515e0e85abf8299e7e473e3cb254c0217103dbc418bf75af45458cf2a32399c7aa0e2fce10c4d55bcd3f4550afb8c3e84910c401c4c4d37ee2865a841cd03b9a6d78d4f64d432e179f4b72e0a3afd7f427017a90e82cc74b4d7f9fc4fe639029d26b0fffde879abc6760ceafc639accf5df66d709ea20ee5704d4cc3d3f57a06f08ae1b5f35b00a4e9226271a625943362f7c90d3d159db06d871f1d0f7b9478fd9723089345dca5de9eff60d071c666a61eb5ff50965e2e53b9b47264bb7cdbbc763c22f1660b160b16f1538714b739828b62203284250247d48c591a1dc0043aa0c6f62fea2bbc0bc88c7f7ad3ca05ea19563f92960896ee18f08905f0fbed1db138da0c8fc95ff4068e0173b5a711d2cf2ed50775733c98ab0c03f83065dc2ba8c55136d9dd320ca875cabef242c30c13839b6696989bc066c0614d9931b20d3223cf74e3ff9633ef12d0dacbc8102a26455c13155e03d362de0e5d247aedaa86f1a92cd0d8bdd0afe0cc9e4943334fd0c34a2d525da38c07bfc1da816189923080a1c8e622fd7bc01d899fcdf7f43b9953c184baee14aa36632d9c70735df157e45ad807f9eeda2a9d79a2282f32950377cff993cb8d74fc7ee9e7d3d19cdf878822f61f503432f8c39e8c71975eca8ae448b6bc5a099b5f2ef04aa3fe1477388e049737b401cde15478b7f8be3b94e48d3cb5809d5c80ad5dd6f34ae08cdd4d0e5d66fec7572889fbfa704fc22ba611f997cba0b36c918cc1b481b30b7a398fac8602da4b45d1a4ef1ea6f358f7024f35bda389a3188bbedc90d9617a74c89648a0f6e7f806c6abb270633f179e97eec1818ae6800eefca6fb1fd16f53cd47c7eaa3490bd02c1fab584108f2b6f36a51f228c394086fb6a6dd11f86ca51b8f2d4d821935e6da7b22f97a07b5ae395ab51a70d067ba58c28490ebe8de2ab91dc51be3dd423bc6edb13506955f45d3d55f9e0fae6b6ad44700ee246e16064ccc5990b95622b21de1709236e0e734bc2696e62bece1b8ed9e82f21e0c52526825b9db0c2afa15434a2bca6aedae39bf368c90cc6656670a48ecd446b8e4953eb5636ce4f44eaca0cb7925123bfa761c1ccabd2e33a45121419453c149917459d059e766d683c2d1fdf39fcf69d8af7f00ce17a54a7b4500e56e9a43bec57051260e5278f1820fe26a3c562491100fb46d7eae8ce5c98f3e89ead1703948acd456ff294f914372802ddeb8e4a284c02b21a685032ab7fa35758152dd0e3ab57c7d5938f2bccb495ab40051419385c5dd5db1e966a06e126f5338cac276df166b2dee5dc6426d8bd9b3177c832a1eff906ae3b11d2877061462e4e3e58d2b6c95fb4a017f04738067b1f57cfcfad115ed362690bf2bce264fcbdb9593ed3a9607970e62c91949cfc4790e1fb8456166ed83ffcdfd1b7f50bd76715294ea5258efc96002eded43761ddcddad7865d00211cbfec267bb969504f0e83b9489bb5c8d0d4b69bed57bc384de3386286ef70e463e1ac130facc561b41dcc5c6b913670de56cfc4de16a261a41094854518a5564aee35e4d8cf2a127d649632e840a4495b996095e5a85c51c368af7b5a14a1b1aaa250ff19e0f3a5b3143fc4f71f5767513aeeed6d316be98257fffec2dc57c1be59646cc6781e04277d48dfe97db92ba189fcb4524dd6b3ce485b214d630d2c257874e2b98881d46d221e5411636308d979b1b8b8a700a57b9f673f3df79ca618c48f91a4e46a5098868c7dcc522df2314e32c1eaf4e45929e00153dd433bbfb23861c7bc78a00af7e43317d424827b24e95219c32a4ec1ce073b629533d94f27b5b31258e6f64283b517f519d8ee98bb3760b87b2870176ba347c076d527434722540affd017e8c48f32597f6d41d05cc61bff24bd525eb723bc4345a19712aeb667bf90822d55e12f8f4554add8559f34b6b55ce898bec5cf3a2a082780077b5c622499d6fc4d0f19b152184e42ee436f368108c68dfc97f42f36407561d748af5329fc7efb5f1f407b6b3c035a2c3bfdc9f50bd263ad353b80abc3f0c204149d4b3da1fbe89a6165febbde56d6093691687dc0e618c1808eac57a0fc2e81838ae8abf16087acc5cd85bd1af89b474b09799fc8c3a2b2a4bcc49e17d9b6bb5713d7ca9e6d68bd7763f6af91f758cd865049cf2093cf8928059469c8cfaa6a1c025702c1ae25013459089f1cb92ea23af8f380bfdfde54c6880ef3fa57898ae048caacbd4c64f4ac40acc50ecae4998f33461b2e0ac765721e5b7d223a1aaffa508af8dd17ac11f3fd0e7c9a7f1f7e6bc650068ff821a467f99fa0abf80cc14ceee2080e11ae9d254feb5c0b7cd94907d4b190a66df90d7f25c3d75c5e0645e7c3ea029e745698ac4052196466bffaa74884b8be07d6e08deebd1ec9da20abb8daac2eed2acfed470a0620f66da2266fd38981d92816f6bd65dd71eb97c190d496d26d1cc9a7dbd9d4952ffe089b5441a858b4de0a1488a3dda03dea6e93b1b08fe2eb72f7d4047dcd3992d4d133b142b739dd5982ed0d1f8936fe0e3026275d24add94671802051dcaba46c26583fc64eb71a976244074a0c24a45c3bab132606618a2d617c4b9d079ee105020cf636fb1557b763f58b7436f719e55c89d80f8fed3baf481eb8da118649241616c426053919c1a8316e26773ceb534bf16bde4c5e7a9fa949a7cd78895de091cb08926638211d56ab95c04d856f4780f0291ee5982836bfbd3eded3a637f6e53c0cc36afe6d32f25736d1cc9eff676de225502867014fa69e5867a3e5c7ff3e0e4e51fd15cc0202d982e722fc69c96a9259ea207959b553f66bc382abeb4ae63ac2f51fb968e31b9f27e7562ae480616d2a119ab338f21211645496b86051dc377de2c60757742a1fb30e35612a6d2767b0d3b23476a0eff2b3d8d98ffd6b2e7fce7804a97687d250b5b2c798b6418f4f0afd8f863d558d6b8d8f8a17c634d7b7efd776ce46ff8eb98f1b9f025f8d3b08220ef82c7ac876197c15c9e385b42a6a5a44f0b01e2c2504535e28a7ca9d93c2d62760315597167d9def1a92990c9b4faf9dea2c11ce9d537c16dffb4973c672dd408ea95c9520f9ee47390e5b9c4cd5a2c7ebc9427acd9348da38e307a1dd2a4ff9e196b5c004c1282e7ce96f5da83beaace3a5b4a97627431f329ae9ef1fd94ecc424169339a4b1d48d72c3af432bfe4d3945a65ebf10efdeb550ebb70b3fc5f8100216ba19eb373f2c27231db8d29155a74af5b1d381ff4e67ac463c76c6c6502b1dcb1d211a17f14a751763fb880ce3eaabb582c4f7412d83498a14e688888df1bea10000790999f3246e3cf77f91b422b1752327e599d53b80f24c26166cd6016fc0866fc0ec866eb496de31649b45fc1df0a17dde27be7b6a3be47bb1a06b753ba087011380300b503953bfd8a0eac2c6cc25050c6ab80db08a88fdcac255cdca5370ec6bc91c35853c3ea35c2fb7072084d6b2138696d99ba93f99cbdf2705008cbc5d959215c2a88e50b6fa922c2c9bd32f1e08129e00764da25f354db29594c806c7e0f889cf4385fd43897bb47eeaf5c71062b3f2048ccad90ebb16546204f91712ecc6fdb777ee8e84edac0550a8f6850324e0bf45337126be38f3f4ab2ce548aa380942e5f6b0245d78a373bb5ad8a46a61580cf6e0a1a3fbaacfde30869c3cae236b72ffc04a92ed99e5298ae15fbda784ef3d8bae93005c9cc0d3dd96b4cb5b4b1edb1b4def0cbc38259a924f098f22dc482ac537b7326e643abed02fa7c84c7b8eabed5239cc75c5341d3f7d12848baaddf3fd6c3e055ba66e15e5bfd8663ab5cfabc8fb3e758af9c0926873d7885395bdb6557f61555aa6424507158412bde39ccc944a538c2d5b4e095383db6bf2c2e493405148befb7cebdf5241c9ac972e0cb86a75af19b4f754aa639dc94e390fde6b760440ce720b9077bc260e1c3f0462faee7e2714f147362ba0ccad23e3e02dcbe8d5cc65cad34cd8d4f50f4fc37c549ba3459a2b034681cb8744531806acc3c62ca7023dfe740f8962051962c7a72e64d974b1e9f9b0ea471feea5bbe28c6571f062ffd4a88fcb9075a4545d2a2b0403aea33fde9277b01b3090c895e0bcc893a9b54fb46f55a85241d32c4bc914c3f9b81082b59aaab32d012d2a5408ab36c711f9aa5e4e832c307bb846add8841255243962405f00267cec035713d91afd70ca40a48e704fdf09d561822a237ca108bb2f2e27962811b99277fcf9cc829dbd03358248b71b89dd84e20b8dac6b3c6a7aefb6105d174849e875844fea7a491be138acceb20b49cf6fb9d9c20a6d29b6d2ac9955070dd6d3b2312ec72812efc8696395d24f5a3be50a8d083fbde75a59d757900c0dcbfab73e65ebe8a6952aaed2498548041e1f0466a5a6f158f741107e7707cb5c3a8cd921d7ddb03776750dd45827eb8dcb0814e5930131023d099bf23db560639a70c9d9d8da46dd779876eb52dd355dc08bd404a0fa07d1f8f6bfa07150a599cf9b6c678d9c2cf4c3200ff6cd02169bc392ed7787bb0f671257ec42c85c74535febdcb3d9a6f7b499d09e170334c3122c57faacb5b118ca8c52bc650de9e5f1d19041185ed47d690af62fbbaef9b15db569574a0540b74622b73d48b615fba8f530cad3e6247b53aa01c5de078b91d9302fbb8e3bb65750f7094b601beb53c4958cc20eb5ba70414d531c3b2af5a4f59f24ab6b446d3fdd42884cd9728aefd614b4d4f084541f1219ec5c45e009c6f53a37191fe9ed0f3872c6dec5a8b9af1ad75f23c8abe560850b6c960c64c19d9aa2b5b5904b4bcdb2555be02f4c9e2df14cf7383379bdb541e9fcd0630227825d0d904cfa1a1548d35287c5aa19c9d762c5dcf2777c78ff017fea3805490143dfc00ec9b54483a188555bf613004854ec482987a7eff3b4efe1d290eefd9c59910e3b9a3ac0fc61624f18cabefdd957c3a94c7d404665d6e04a9a6e5319cd0998931151e1e99ead54ff8821045d6af60e85fcb39e1485b08a03778af70facfa308d5cab2581149a7eacf34063223d858cdda7fb83c8b6434a7562196e25de92ff1e361d77044bdec2a28b5472a7b0af8d40dc82c103e1330c72b8ff4bc728076e8fb9e16b547ec4ee28975ed92d4f368819af748f49061827a5cd6ed2e734611df6f863135b05b5b878c0667c8c2f4270d3d5e9fa1699133948d288081e374d53d81b1af543e34eb7a4634aa92c7e33261a66715e86a3b1d0f2116d36ff98bc3d7aeeb719983a4c72ff24dbfeb1d1d5fb15c02fb3fe414e107236c36dce0a2704a485af17ae08d2a5e20089665e0b61af3458da1f24ef05520a4f32bfd6f6c409bd43225b19f0ce36b2f25668f826230ec465ef55fd8c04a110e6dce21e2a948da0f90b31aa3bb1e0fa7aa852f2023632fe5eba1ef37379708036ba4da5399c9863994d891d041112580bf21a1af7529973f74009c1d719ed3857bd033e7cac95e57886f3856ce2c2cc48eb20e6b38c06d7fd16e1855aca0d7c65c53a2bfa25482f2d84c54696ee4b477467e80376c35dab00f8ef9f82353cea777161d5a6914beae86451c933c9ce20f49a5390aa623f81e303c0074f89ff0936ae6ba3d674aaebb6ae38ad59a986d31721c736c0e92522b43cba0e92baa12ff55ab6b3b93f882a236c4e2af85a00e862142803b50334277b2aa0734afbd0c7c6f8c8e79b6ed6e002a745d16a49b0adf0c0d547f08cc88047ceed7128769e5d0431516303922747d0ee1b2b0a50c9cf852e06cc495f9f9444d567b04687f8dd5da7fad66d9515a22b2b7b883f18454b9416e8d7f652243d91c87dda6a44322000e396c474c0638dd1e17f71937bf35ff91f28ac4d08ff887f360a318dff74ac77c627322bcf6c2265f8ed490614fc1e1a00a33cc64a0c96c3dfae7670f72d056f895484ac9b3498cac4b3c999afeb840b10414a00d97b93d742c442447f868ed9fc870b3e1177cc1c26bbf942c2ba618ec7c9256ddcf085266c0af46fe2c6d157a790c418fb0ecbb64badf4c0df9e726b5ee37d3bc8c420433fba8e4dca23866a62baa417e9e658f87ffccc5d48af52979742adc444a28215821010df5fa9ce6b152a77bf7dfaf27d65bbeefd0162c64023e538bd572a01eecd29991b773ad106ca214d6aa44f1fd91f6a6cf31a43e387e3874d3105a0ae6ed09a9f9864f1ad50934cdb1947182be09335c5edd899f8f1ea4eec1e2113c37feba38be8d748127f44c108558c47a97dafceb4053d708888c8b2e1fe5ee2546a93172fafee43cae79dde73f2d0c01f62b6f01baf986fff56902ad33dc7874dcc004cf3c006d1d07f5661602bc46ab8a4107601fb8a10f2ab8334ce924cca5381abb7a8897e41dd33965805672a8f3a8ee3d755ac0692d55cb8898b89c96a7c3b8a0c0369efd44e3370ba7de8b843c4164f9a66cda7169af77c899cab70176700113848c881d32c356b77eb49a64dc53a77d8402a88ea8a8d6ba110a2e28e0722dbd82d2cf0db14714e4815a61b51dd0b921de5fc70d782af4c8d2dbe16d2bef169d5643c4d3c19774b7e2b44597d68c3d0074fe3f0e93dcd7a3f4b7a0c26a0240c06571990712f4023cbd97561b7c79e8b91f9ec841d1672018dd9b99b7fa51a7310d07bdba4039705a2adbde67c9493b15585d6c342d3b9a7dbec19281d33415f7b4d179de08541e1be609d6ed91f29b28804310afcb2eb5c0ec40c5a00ef15beabed562f8f502e6f44d5ef5686a0fe4b6ff80f0b1e63c3cba0fbd5105ec317e88bd14b01f4243bfaba7f10b4e759166b29236cbc801d894b14f9ea86c04335286016e481ed3015284e940c2c7a70778a9c4ff26623c9c3b0d56db872e4f19cd36f7efabc23e2d65f6e9371d6cd2c03dd2093ab27cc3a87290a449dba3a1806a67fa0854cdba8a3be486b0594f5c06cd82ae3b84a7c807857d237aa189511899cdf7cbe6a302113a5becf056044c834c30d4cc43549ad62a97c5e93e41a4e7bd286cd488ceaf9905b9a31fa0387d94173189bf9f477f536dfd9445c586462d3dce1c12638bd3fcfe271f35b0f98d6dc365733353b8a4c514d2bd904f57105b52c8fabc3177ab7cfef3fda3fa764c31836601f319edb3d47875d6324b1fb3d0c7e0680ad82ed1c03509b66fb25f18e95072d5253a996d2e2065ce020f492ae598865b4134bfca553d827173132b1f076f7ca20290ab14273a9376c8060f66c6543af4b6fee1631a93b1d30c84b52f625036e0ce77ac10cc7ffbd2538334dff13a9ac81bcff2f96128462a836ff2fade1da403076ec1a85f3d6a001f06b5d925a48ef84937e83e43deecab8cc8cd272d558bff942e67736eb7c237032f7699ffa68d597356821a1b1285d5337a4268e2a6d03fe4ab18b13b7bde33733cf0c9343623d984735dc5341b83fad40643024ce4286bb660a1bbabb454b1415a4ea8eb35621922e6c7ae8920154468bab0af4603c63222121d12fb5debfbc8c289a4a44242258363b3e39e177f1660ce3a98e6bd5500c91c36f72b7de0f677e7fc4155fb40dadb3092a8766b2ada3055e2c274b25d11b9ff8f4be5b45586f4ab5a36577908da6d75652bd84e2a2d97c71733a57002868bfd8f5b95e16dea7f4ff858ab54c1acf8e2722da89a1ba6bccc17b6b180fc1c1b0618c66cb2cb3f26926911153734be86513e9e492d77f2a5067c5bf33abb971d1e50d81429712e1bb75ac6269be0157e568f045d76512c48885a8ad7411728e88ec6bf735180b644202a679f53fc78a60b7610d1a57a84ae5aea9cf9045bf6d71dc9483259a62a4df9610ae2372db487ac78900f5d0f5a457d13530edd8e094e48797ab295e6606e8f057078283a507ff84ffc23a2b449e88592195c04dd873d43da432856c60610742cf114b5cc919a2bc1cbbf31f53387cba2ae1867f7702d6b5ada84ce7b55ce973cd02513d7b8776756e3f8dec1eb4e6c1527e42398e8b8f85609266458474aa0767ac91e038f16b23a0dfdd3486168ba6db9968fadbbda45b33da19c356c151c537c3c87c5a457634ffee61dfcb0d9effcfc296da2308bead331f327e0271bc2246ecf00865ca771ed2b9708440c35fc5d0dbb2e37a3bff47d68cd4c0453de08309d53e9ae75cc9364edf0071efe7850179edc3b41cd671f3130a6ae0ea3695ec923936b2f3b9b96ed8547dbf321d5393c2044805d4763468bcba87e7d47855d5322c37d5fdd2c347b61d6c7c9d3ebacf9387b4d4fcfd538a8b24e882183bc10fa4b9d17a0670228779ec43b7c42e54d8a92504a210415735d78e9ac250f4ce3cf4571657c87bcf6613095f828c7b570f711339bde2aa211d97161f5749f18db7ee619a8ba22b7093db67263179803d14a456da2c5643a3287780aaceed98c681e7a307d2735832673a829e5a16a2ad58a4a8644120086487880cf9591dfbce00d0d786695bd9b856e0cca154682591a8968e3064903f5d906c0dea961ea7be8f6d335d769b14c741f5712508d3ab48a3e1ad78db34598e9a9463fde3f46a95cc5c91fed1855dc05d7592bcf9469b982074e05bcc9037e46e9aed584dde8ddfc88c870788d388b94acd9b1be94b1ddf4561603e1e61af5b787fb9bd6cd3addf79a057109a711316c3655523a4ed17396b16dc45a171be59cd01cf334672407ab876f406bbad039822a984dbf93387a722fe0bbd33c7973e490a33ebe31c4b69425861bab32c5e7d951e62b6dc71ff9a81726a8e4ca440bfe853ea0aee7a3f3f280724b1995c5c2668289e4f726ea063c9b6653219e5f4fe0fcf0c122e9b216f3b652ea311471643ca4f74994beff99acaa6336111d425fb25024395879f2f8346a2cb073eda606c3f3fff10875c37bf9bdac01f3ed99a760015ebedd63e7399aedfe96d84caa6ff433a2d58619f94e358a49eb36494cfca60478b72a14652cc3fb27237913b2dbb8dd6c67d656780b68884b91af0b4121bbcedb5f6eaca825ac38d40549a688189ef17c74153b3268a970f90b9c8cc685b20c919a1f651c1fedfbb98d7cd4dde3bcf915e7ca6109abbdf9f61cb71041768ff2e743ac98c049f0e6d7f32ca994bcc6d1bff8b5cb1e0cf5a95514701288fd923053a688044155e3dc17f706e9faa1bf73e87ff128fdb67eedd040bc94262cbed2425fa0aa8a830134203f13a1f919616ca41ba872a7725e9f55c6fc3a29fb4fc819411a682a79c99920b07535129b1e56056367822eacd4077741bcca1095d0129cf625552fdc784cbb97b6913347b3ce893c67f9ea27ac5da75162a24b9b8b7dc5244f38116af80f7275cf96ace4932fff981613e2be117f50e03178a4efd6d3e916f3f7c2f52521974fba7f3c99e58810e6d5f3478526b824393d3b410ac2aeefb928de2b99c00bc38bfd19faf8e5e4796d14e4fbcb2e26ac940419686d8de6625c4e6ce733c04cb9f1edd95d464f6c0a32ed3eeb8656eeb77123946a053265645da5be41a7fab0e40175587a13151443186bb2e595d838badf7c61e5af4b7e54ebc991443d86f75bcb419cb92a6ce5c25de3251175fcd3fb8494bc79147e043abf26126e6a865ffefc2e8f79a4a095dfb2bcc98e46e4d059ba4e56be718f1768369f8a25ccf739be080c11fb9042beb037512bf260a4e52174851fddf5d4fd7c210d5d7cd246f078c5f6a2becea813fb2afea940ca0e1f9a698584b3a75dd238c62136450b3fbab351994e236df89150665bf0c8cb6680d251208ab8272e415242916fd126ca0aa8d2587fcdc24ca5d3c08e87c013e41f25bcda24fdd114207cb163c0f01475a712865e97c48bb21a081b46090c442c024df45699f94693cdfb109ac99611154f273104cd7f74a2087b9f43835e1594ea6416b7f5e4b01773fa0d9c1b572d5265c92e996194d36b215f4c978fe94dbb3de5af5c81868c2080dc60ea8cd42c8ed2855c48b510d97c2984f98830cc434547f8342eb586dd48caa0725ec1216eff34f8eb3a747905a5e68109be07276b152d42e931b87a8d9b4331b37389060e6c1ac05edf63d84d2271d12efa59f4558361eb8f94f6cfb6aad7d762587a61e154b4e1fc4dc8be57dd6f9fb293d5480b5c760f44aed8915f1b407e8b55343119dffbff50a4f6c42477b15423001bcb12221a9fa4a9c1e300defe41ed4354daa06783251efd3bafbd84f4f59316aa90875e6c6947b59bb4b8fa3ecd9de0b3f94ea6f9de7a6e36e6a253771421210b107747aa3759f957e90d2f93ad5c50f46c27fcbaf7903fe53de9a79bfbdb6d46c0cf3d49eb545cd00604150dbc87c5d6eba342135471ec3bd041bf7a3de9915c61e146525c8db0ee0673b4388febbbb10ac6fe5ee017e3f99c8155345ee558705ea66ebb6faecd1b6d0ee40592bf07f3b0ca62218ee4bb2a6fc9795a957f2b4f59023df8baa9f5c813a7f2d10d31a572973c83c18bb922d6d00f786ef11fcafe7ab6f7c9447ffdee75d46e4717ca380b3a2deaf08f4ecfdae806152288d4b17f69ae6e35ce9ca25c42ea5ed69d020ca794adad5ba62d908451bf6b31333176e7eb0ad89334a625209e7fd13e36e18e69385a23de8cd971a197cde7200af7cf0dcee9173487ecc8e9fea5d6739366a5179ba9b6ee0f78b4dd042fe5eaa53d0d23173ed6e0df33b2d0aba5aa795539fc4bdb8dc40596209f8b829a0ac0f7e55c1f83d77e6e408847a3d313d0f065f3fb3af29e73c4a22290b3f7f5e78d4cbcbd17a75e620b2ffcfe8a2d7c6ec2fa39d4d32e1fbd1ad7e9aa51d47e9e423069c765cecc3cc922d7dd44323c508018bffff366fe5f2439b753dcc707cf20b5cb348b6b74a390210d8fbbb2e2fcbb36a86d2d0bf52e57198120399f517e03d9045d1ef81c0e0340cb425b5f90bbaf35d9cbd6e3dbc5d7c04337dc86aa6e8f09de944de251ef6e30a94cf3439ec3acb9c64bffa5c6b9b8c1d63aaafeea759c87824e8b080373d46b94e55942d7012dba8dd812cc7f509ac0d97ecbc643a6eb6ed78df7bef2d60f27935293cd59db95da760a442da1f076f1dacd778da4a51da6013d1bb937ab99a763303accffc4d184547586451aa056206dd92c94156c0513cafe5e1e2deabba380f3f61416aac4d64bb6990f4c7101b09d33e362d307d3e1924dd8fbe3bcb153efa0a784573289e212a7059ea64394f09cc84133126d3270c4405f5743662472f4be93cee6aca8f13b6ec03cae3b12776977814859bb875ce146ae5d0eea2605a6202bd895c3c7ee974ae32b74adf341ec7922fa7f4a9f7668f68a7eca1537d92c247459121adfc11cea58055990e3c85d9b7baaca6ee10a5fe991dce1eef4f6fe6b89aab4c01faab26baa0538d6937713917f19a8581275ace4b30c841e2ed4f226eb87b847beb6991e33a3d69394963e6fcd74317162701df3aeeb0a3179ebfdee9c0764697ed0d00c716b973ad66a7ce9981101531cddef84acb601208a2fa4522cbf7849177e800da8b86c26db7e32fa70db310db2f6c577802aa2196e59238cf8628bbd0ba6416425c9d481a6f92bf217be32c30f2d386bdea7973e726aa009767bfc8c98761d7a00d51ee0907605fec1f1f0413bd1e936440c355e19660605565523090edcafaecbd5b77b8f902236a1a5e7a9804638bde83d0815efec36bf2223b37d7a9b0548e4ff8a5f5afef913fa1b98c84554ec5570dd35070639020065421b2630342724e8a01849f247e96bdddb82794ab4ec13c03742a27ca38e756c121b378e37f43c15e091c31bb226431f18a5be622817c07523d29f55b13dab102a3bab269ce232bd584ebd9197985f768b38ef6b2e88f4668b5826822ea1803d0a11d68ae0b69fae08488a4f23f7dc7e301e45caac66aed8167c1b43c982cf673daa792594a81aa9d7eb09f08dd650911fe0982dfa4fea18e452c006f576e949d80ab6c2622d5a2018b00d21799b2029f519aaa0454b682554f84f7652d6cb6319a527ebaaa7b0cb96a1ee8fb03276988db6b1fbd9df6f722885e2b1ffcc6c4f01b6384b7243b3e08ae0ac30d5e8011a7749fc302269015051fb136581426e44ea67a8d0af94bbfc872f461436af635f4eb12c174dbcedb4173bfc332e3f5e04a896e2fd52eb32394d58a3cce6123bf64ca8aa3c3aeb5a19c3580903ebd7a024bb739559bbda2f49606f1090cb8548b0e656291354fd206d16bcf496245b5de5ea8072ca3d628854ba1f623ea5701912f072f44f214f07d234e1dfc3e3fbaa831b0af3de386f9e108f89dfc55d1ec93cd4ae61abdea5e548337eb5dc8e0a13ea84a0d3858a3b055a10255a203571611263003696a65a5a45d15e325051dcaec68f0beb6c5d35cefc09cc6b3de487036683c802b522a067269556e083b81b46794ffa1305766df7d161bc4d5cc3045169e3d4d36e0396eb3f7592612cf695f236dc628f1edbf30bd00e77e265da97598b716e851b2977fe4c0da8ddc99c9ce12ebb67987b8fe147c33133db0e68a94b064f19a5427fb7c9e14780ee27e347cf2df636f7c0173a1fc73c08d6390384498cca000268b6b791f2c378d97acabdbd7034514fbbe3627d02b2169e37d623931ece26dbdc113035fdc4463b4540c553292fadbe29a66bf05c0fa42bd4f3093e7a1f0cc15d341f251a428ac6362d3e013cbf1c4437c21520934979ac4be1b5f52ff741c728621a4e1b293d96007699f0cd6014cd7faceb18728a66315d2f6f7c5c6f8ac8af8d9c6c75b52a41eca6a8a2bd97d9dad8699d1a0f86a01e7d2f339c13331eb269b69073f065f31a1660ec49e58b76d25080e178bdb4da489d758adcacd3e1abe81a72c12afaeae9868d0ccadcdf226fbd5e76a7a10c3cf2f1885cffb86fd51de60c1f80b75ca9a171ab15ab531fb0c03f6155c3be1a76d2b0679601953eed1cd90eb00ca792938eaea107968da2f875669ed8b41d08db26582301106579bbe8dd489faaf02141f902b55c1ef6016a102dadc63a184cd18ff5ca18a413bc0b87c8d357dea76d4beedbbd4f51498182539e1c3f4049d1c43d55ee24a1580e83aa923307b33c0cfb5219ab2b256ce41502ffb8db44fb227ca4def873742383b2660cbb2dddb93ae084ff48a0097942348ee2d5912bb312f2e79a9ef2f6cc9d623863ebdc2f4da4d46fd9451af092ab130d87cd7b122838d6c390230acf339e966957fdb8a273d60ef02e37aa97ccec7a91c715685fc1d4a264c3c8e0fd5d7a4319b492a49756a47af3390678e6d412c328fcb8ff3cacf52a496f596d5db5ea9d715a296ded19914df5f3202913d3232d2b2624928ac3231fa1675427ffb87452c6ac8613e373d962d51943585db67be5aa04596264da677271aedd0862df6380393866b9594b22403598919c0d365a57f37a1f437f2da86eac96f01f05401a2727981326f3cde817bbedd62fc157530d5b5b476fb93e29a87e7da65b8106a8fadcf04c65a239e0755172afcd0f827c9b4e44e47ebe90b6081d7019233ef209f9d70aa24796db0035b85c6d7359bca8ccff4c46b2f634a2287bfe977db5cc67113eb283344f1792c07562a394560776850064999a83a52aef5dc7dc0bd9177504cad7231cf17509d713af85875eaa316af689a29f10c0ab12c94be9eb3d1cc6c93c29fed99de1d5c003ce2871b2dafd1e41c841d5e90429c6d3eebbd0c084e385753599420d631767b8d9bc52abea26e9e13f8fb590b06dc6e9b818423a3ff7aec0150b0bbcc458227e63c0e815eae91bf446416790f0557c9a798a6698e3aa3ed3848bf5a6b26d61b2dfe2f3408300f91c1af3086160fdcc16a5feeca9a1f93408d65208d007c0fd5a74388d90707e04e2ea8afba24ffa3f70b7a374d73899aaaa9409e04d0d447cbe86522986d1b4fb07316f2417241ab465f5a95a52a6a3b717f5411e79c49b2dee319e9f87680ff244b10b95d540f2c542692f8b946ec49ac176c40dd0b4d888fb0b704fc5963c288861e1b805d8bfbd86c4878affee1a0fd83dd815c5600146a4f69e97a64573a16b2824eaf5c84c3efd9900c741c30ab11f4c631cdff73ee70759e5ed39c276d02a2a634f623a84846abb3529f2dc56fe71e3ed9c968db0e45edc77c15cca4d485682b2b4a82c71edc2912a395ebd1306dd686ad57473d2b16c32e89c072ac5d4c641d8034fb65a62b9346249ebdc3d92339d9f72cd50bd2c6cecf25a19930e9716f9747c2daab5de20003bc00024e0ee36b6fe8abae19c155c8130b6abfd698934ab8a3588a658246cca6de904b09bdb8130d1aaee2e800dbee9d26ef1370296b2c07ef93a378882164e0123b4e92f369df769ccb0fb84286eb3039fd4b944a47f6099e2ac86ad077de261738bb2d06ac74a8cce089b5e0d12dbc1cb57a4ad6e6504fa9ba4fc43660cb6090c2641e847bd20a85431af6e32d326ec039f6a1c74e2d5534c527e8e69ad71f5bf3c253c0a636ba6f659153e8932155e9f0a747fc662494691d36b3e6f7f8f50319663f35fe9bae523cd9d9afd6457bae762d9ab33ef0b4bc67c88a0342aa6389c792e0002e172f1c4e1d823bc2a61791c8bb7275af07b49f619d7aa40611e21fe41973cecdb42c259390486b76d5577ef2685548fbcd823bd48f3e8bc4be87c513de6cf58f35d3b49bbc92be0cf5173f1873c225f9fb836b49382e7cec3bad2036c01242edbcd73a22dc3a15db832cdee0d5bc5c250a2b2f529d6c778d637f729af0a31585bb6001fd0ad2082cf6df7b2aebc78c15cceb2ebf2494adf4a998555c0429c9cf5e35d1cb8cacf0015243fcdda4d34e945c54bbf6d60ac329aa070dfa1cd6c123ea79b4998e3e96abfec1ead5d862c9edc875e76388c16b93d175420eef30c5307a4cd618497a628bc971c17b5828c02b95d680036fbe464409bfe9877e407f0f8a64343d9ee45f3c7f12778849384467194d928f37ec65b008196ae4bec08cf6337042293d70b0719219c18e5c0ae8d8e360417be0afd21659f7b488c3b731a4a7f01555d3bc164e896ca49f15a8fcf00a164b645fde64a672891a11b67c4357437c2df7cae95aaf7da7a1fc6d0d57a05250eb639fadabfc5765e050454f677299c0107baf69352eb3e8a49cd6ee736348fba02598d5ae594e64e887e8bdc731d1e27d505a9a848beaaed3c1208eb91b263871d8d1f266d7f940012755706f4d0799f766c84e6207dabc4c5643653aab2667c13ea2db12677ce946783f54e7384e20c70cb19ff97b792dd45da49b5e635b501590048245c63c481650bb4e57531a5952f079f454e1fba7de7d2296b913721dd703e2f8a2295df68d994f395318c42579469d0b1dcdcda4122bfd9dfa6e5965e8f1e68bdd23e4fa44a37a6a50829a8019d60949cbcd53bd80011e274ed91e615277614286d0f9f2204fbae3032e08f94deb6f71f1f9be22b4d5178ceb91e0f3152a01f8cfb6817fb9f6bbd21d226f7eac5193cd71e8ae224d04ee708e2ac973cdb51c227db809e55a2e64c7d9798d3b15200d798f67c0c6c41257d269d27fd733ef3bd64bc752c8ad5a0f5fcf59bf7bcbe3333a86db3d6b45bfcc7cf74cfc01185d848572125caf3c97b986c4b2fc488aa332c9583c84ce8f40ed2e2ab292ecfb66a9a402b2495275005d64953bf2c734a6ec4d30018d971f52acbf2c08025a1cb66962d3bf047856cf4117f891affae89c4e8f158508976f00620803c8aca30e37df63564008c4eb0a71d8a33f42ba254921141f5ed9761c937514a480b95bc25a7fbebe9aeeb87ef247cfa3a6007f43d5b73ba6f3b230682ea150c117ebef156dbce52cdf46c2716958a24849d5988722921d56cc538a9c07fa181bfc9670f09d77e98b545030c81d8fe723ce54280757af56b8a1d24fe6dd21f337d8ed9f62a98ba64714506c9cd1daf599b595ff55a9381e58f9dbdd3dd1901ceb3d7d3992b763e57cc0fc5ce3f6193d6d6b5586975adcbecc53ebf9ebe043839f40e12fe55f8b95b6a31521083c80c134747d52f005debd0f2232cd0cc8f12cd8e0c83c69eb434b5c561f7c6780ae2aa04798688ae42ce9a3cc9b03c76acd326eb2c3b9604575a5302232413b47282aa953c0afacbb66be76ca602e8600cac141d4bfe3f288a4571cec774525d2af6cbd0571fbd70e34b139ebb299d687d1867d96448bc48faf3f98ba54411095c82d029610055090c38a48eabc67df3fd0f299eb50430d0f5cd71bcd5248ad72732e874598279712c041f2da5ddfef48433021bc0436b6623fa7a13e83461ba21618c7afb791ad884ce301452bd6c14841bb418bc5ef81989a0f88725e2819775b8b0208efec40147d8b25c73709417aa0918917fd12a0899e2f3986d2181f9e53da0bf699219e8c14a075cb0f81e37d827d58642dc28b587e2251d54b885262d6d0e801dfef9cc050c50cf53cf9f3520df8de4d23ad0eba38397cbba6e1e152998cc2bcd6d3f3a81492d87764d7cc1ac63e3b9cb8836f5ab247e5bee46db255ac55e9ba64e29abe13da46e70a84113907ddf5d3810a9d2ed2e315c221e07838a5b30a5baa9f45fbcdf3fede752e5b4f1e6395385b09f7e49add16d0d3cc2ae4fa95b5d0e7d89ec10941ce4b159640276b5c7d492c95f09e4cde26e8ddb401c12a02ea32b49df63c01a0a85092cc8bea141d7973616a18ff763a1b1f59a2abad7204f850e44abc7e21f856a40fd92617ecaf6d26698962366c4c0bc29d0862d5781040d948404cf81897c276395fb7b15f36839fb634d5d3a87aaf89b7481bec9ad563cdbe247597a31578f20f28a60e7c6c7c8e2e969fd56ef70a59a3d65edc4473beec7f91e90403f31bd27caef9490ab9095e1574b3cd6ceb860bf2458811f0e32e7b9ce27bf44d96b4b1cedad8d1851dd89069bdcf7788f366f3fe5ea20bea855f37f41297c9008a67300f0242710a0cfdf5350d63c1bc63c81e3c927980849247dabc7eb608c1db6e7ea907ba665798844e3360c0752b74135b0848b4c8f9ee39a01b6917623fe8a878446c83f5eb37a11df505a1a8e87c5e137836286bc3f3f30021f65b286c63e1d66124e3b576a73436fe7ee850d5c0fa716d9cbdb1505033b850225a05666a3b2c78dc3d2fd5beb2ca9c50cdd45f50350059b885b6f42fe77344bc9db73defddbd5948d5f658ce9d38997c9923384f3e957b99cb73bd73d285b64e784cc8cace097e3f84dbb1edf416eee171d83aea1d3e20ec9256062d25078a76c8677662b0a13690216f91d04ef3eb3cc958efe0dea089cf9101205090fd130c7e46846e0e222404eb67c35c4d8dd42d213b02a067ef9cd1c671fdd9d8230025a14535480446e1f5def888ba480f681f2f723eb3d8c45fbc974145ef3ac8cec261b6febba2c564e66d731312350721b97f1ad2fe798e73d19aba6b7f9aabd7958238ec9a3a3ca2b8cf406aa0c320db9be7151c672e6886ebcfea0605641446e54eecb5799ccf50bc66caea821c86f03a96c9eaae023cb39d776dd9080177c5577a69e2e6df5526dc6980e2bdf0357acfea7e4812411ca7b9efa505a9b5bbf4f03d3d0dbf3151a6e2a574300fb76a6245ba1e2982c3960143dc71e93f1dc3a59c096493ce0282898c3dc27882aec3a7c2b89fc9d9d2249ccc717c8a21c7c57d7de50266db917027b1b41aa081d8da762d13ec5162cde68a3fbcc11cfd29807c33917ceaa125c5490cf9e08afe30d24935d5566dbba2c508d69fd1e9b71dd8fd7f59233140cf7f5226786450b30b50a1fc173986ae35985acc0db10f5b7d13a06feaea51b852af4328ed1b4574e44d52215f7b51ed1f7672b4833e29b50b8057e782a8af7973353fd5e0092da94d61e069d9e77b3c4dd0d43cc9f75d3b26d28eaedea51c1ed97c466c16713f8f6b81eeb4f097610dd387249a936d7e2ccd371f56ca90936515e43362ea46be8c793aeae7111c02d5e0f59a576b68d340d937d1d23b3c63ccce25a67a4de3a3d45bd00601f44a1baf9fd351e20b358577d4feaf257191094280e362e45df3222a9d1954420f763fdbf79dfdcd3036cc0acd04804570b4b00bdb0a02d6d305e1322b6b8ddef4087dcfe2b334aa3bbf3edcc2080e0e050f243051b8f8d4177be696ce08d80dc0423c393a4127ec64a286de6650f1882f06fbca2d3cb51d97756d7fc1689bc5940e71d02a42cbd2a7c74f7f95772e497636a2d9b8f1238631021530a3036b90c7ed546c3dae613ad0631a8890fa2dc2934faf25d34aab5cf8d115fa66753e47734495a2b6eca5c08f25863f67a3207acc9f67eca722accf9529d93d280a98efbab52d47235df05cfd4ceac8de811ae8141ce2bb5096103892e83e18380226be274efe7b11054c4046546ff6318f27882eb37c0dddadc9087f76f926e6ebd94d9fe5c3257bb92765d25750c96bdf33ef04e54063e3097b5e9ac41878a3de02a1a03cbf51783864638c719ab2c6fec8fbd16fe7372cba12c48a1a55e087f26f2be69e6a25641bc44a401b366a8b506124342172b1fca5683e22897fd9b0f3e8ffe5fbbad1d7613a7664846963e88bf31b3945a4117a6f294c796054a7133d75ca72ffa9e99acc44a15013e64cc4c2fe5805b3a08749d36b145d14354f01bc89ab85abef994b7a250cff1bf56a3bd1e0cc401945793532e3afe6c20cc38bedab2e65b01ad126ea2994c6176caeaf2c621350653813ff13bc2596380cf8ed8c550e94c5c80786c8a64352eda06f9c6fc104526f8e9b7b552c584a87d5f6922d477fd91b3fca47a232109e9521b563c95ed68c1e71c90fba476ac6b40bd5e38481e4769989b866876921d3b6406d5560a826ac0298c1579712914162e4c87199828739c5b7b88671b4c1a15e42ef73e12ec76a224bcf85b7c50bc8a7034244506dafd0ca46cb7c15ae9a5631082b6a08e91ed513c87ab8073c017a7b33d305fb82b250a82d64695a6f3151ec753f39b4636bed542190b78a28f7eb978a1f7b02bd034c729a0cc431046ae4c5858aa7bae1b6ccb5aa4cc46048f3a6af1b1b1714aa7880b7bd9760648e0e7d039b2ca609be671a8b7b4757c327d11a548d9fe4f7ab9e864cec6fc87ae5df213aa0ec1b7a0215f8909c4213e09b2f7ac559b21247ec9746d54e39bab59497a94ed6dc9a8474103d3a2ab64462ca1bc4652e83c62cdc6ed36470fe8728a7777672a2cea5308fff0140d7c2de27ed531c213ed9bcc2b8622fa4382b814abe8d9086af7686a483cb56cb5581ce34fabec2d860f05dde48796f81c3694451577cdefdc8487091747a49e5e7d9a0cc2b0c497cfbcbbb4bd99240ffcd484736c3d3e84f8e65666581ed1c1242c710b70ecad164b0b0d760fcdf0b8e3abbd97a70062f8d45b29352847d6b646daa0720ef4754ebdefbd41ed552eaf5f1991b1c90ff7f19a32e4a32f1feb12718553370cc46abbe7145343200c0360485c9adf25edebd223f421f283320d2bea86ea8a4e6c709813386e7edee3f6a54eb24cba62849ef8924169e581d76486909704bce5396d6dc0d5f566c5d76cbc5bc461ec8dfa8b29cdc042d21119d365863de2ddeffe1be1b66e22e551bd1ae52ce46af6011de9018b833cf19914cb16224c5f67412f5e2953afcc142dfafaa919d417eefa0d3201e438c4de46ad55aa77724ed0acb8878536d20b9f4120b493649bcc2b1477f2505d4e37592485e273681aacb0f6ec8c6e5da9cb8df5422023691831beed76770b749f5644a74d0751024d36c9165ed8e55075b2e1c8f0a3eec27443769278e67a699420e8f4d02375d971c04c94c5019c610d63b766048c60264ad5c3d719eb9bf8120574d6726f8da6f819ca1e5970477c04d196bf66c3a9cef37bc76b743208373fbcd19bc0539dd4da221794b5a49febd34211ab91fb46b5561f7c87229408caa33598d16eadd8a3eec97e0250a8bc6b9750a8f78ab3c74f5affcab3d18848d18ef2deab02d6b446a606cd8c4b4157ccd210ff0d81dba3db0307bea237beb739fa74629c64c957cf02db78a60b3c467b232737c5a3991c8140be3f921f08ccd547c4889201006eb0eea8b1be37c02c031737804cbd122415e9a1d761bc2598dcc98cd56b17b018ecdc549f85d95f3d9c4ecc92b3ec1bbaeb077751aa8926ec076d72d554adb90c793f41c57eeafe3c9b1c0410e068b5555c75ef7fdaf4465dbfd591435640168d99b1a8b4d0f0dab711d08965aa61e8b616dd01d609480d1b5effbc71fe2cb1da4ada5707b96023de9da8e8f16d388f0d25de46a599b2e882ed315e69be060c45e6788a328e2e414a8058681a793c1b6cb3740de27a8a9f07520d2c58e00fba9718805afbcb5fe70f4c3f3ef2212ae6028b819a8f012c4b6dbc18eab2479513d6de1cb4ed032e563fa38e7677f908e7e88b2d50c52c03440b0d09580e5973866b27b2829d6809b0aef2652fb011ac4d84f2e9610d4e3b4175f12e733be6b88579b2a2a217abed47efe59a3d0621a81b4c12db8e38f2e33445182a396ba8f9a59447d59bcf633ece218d1239487cc60475966a21b7d263ca80a2163a7a70809ea1bfc3825fa6bdb277e833c8fb4fc28a21d669749320dda88d0773bcc6e66e3ec60dd55d607606d284fdaeac796e60fa638d60a5f0c7a840f5a9bc13c322e04100066f7cdedb63ab40316f4c1f575a938c401628c2be50585bd380fd00b3fef688a04d37adcad59af85c8b00f1da22a3b4a2ad552ff6b5c914fa80fb5ce12ed8c968ce6f3859f9423224686c12342dc2358f042dbbbb7f858b04c64ec66e87f5ea5644e3d166d04b4098b229ad3357f3dc188d40a06a7c9ffe1e87c67087fa96c147bd8d0879b8c842f708688b4b7f0d04ea36bef687138f452acfe1bb62f832bc1ea13f7d2e7e49949f26846abcfd49f738a99aa190c3d2fb4057efc2e64110f7a5e1b2a51fe5cf2294ed5d794842fd4bfa76108272ba70b973dc81c95940f677f606d59340664b14a82f012ad595acc7674f6b61eddbd4cb4a1f66078663122eec20439a1d152aa79daedf1285e2b5c98cd179d562d2ebf73eac25cbeb032cd3cec902d67f39474550f36557d4ea52e3be9156f8db2cfab8ffeeaee15751e6079ba0892f888623a7e42f0692230d9d55639d2e4de527e73058b1cc6871b2271fa814141d7c6d701ca308d78e8421920576749c67f20895a7154af7c23ca868fe7682ae56ac615e6eaed3ee3dc2f9a99bfdc735f21522275fb3a953e40f7ee8e4a64e0548222101e1b550aadcb5464be482b51345eb433ccb1a691b5a558dadcb10cc21a1d32c62113b11616e989e01c29754dd1a9a8ba8f212672e2cd88559318c8891c7bdbb7a34fd4c81519a91376d51c28a76d85f4f2572c1e5692e7cf2809216ad37527359ed0729ef40231f605aef7f4ea136718b8b0afab757e5dca5a41d4816d1d0440497ee8f369ae9f45cc3b1a582e6d366cecf3d083d45d47007dc04ad7888570e92add90ccea7e901a42d3b9b5259c2395a401edb455f7fc3d56e6f2102ace22d7ba57fa13f1820ca9e357e2b8609526c01b97f9e95b11ab9b00cdd632de561ac4fb83a420deea20aa78421517cefdd1ccedd9469c33eee9dac5a054780bc8dbefc694fb6c1e4be64596af7d0fc62a0e90bf28ba0a4f5c6ba640ef5c91c884b79d1ca75136645bf057e678500056f46114206e7ede6b2dbee219164cfdb0ce6e3448d4149bd7254a1b78cf514bf54c2b52c4993f3583af5c737b141d217117ecbdabdd7825646ddda4a173560d368656afe2d7425f3d4bfb0d06c32ebc73caab6b66572fece57266bb0c151f48783396bdc670995da4fef51ee8dbdd8eb858df3ef1e7734369988ef051e92c25cd3c2a001aa1e24f50e2530648eb4b1fc2fa654fc532fdde8255c9a84c669a82671176163d2d1f0ac9732f6f806f9209b294976287d4eb8e58ee643e00e6018fc8c5a008dcd39304a08e738e07390d126aa7d1ba2b3af306f1a5a2ce6623cf6c04b851fc3e0473bff24b001ceae01ac9fd53e5b09e156e00ce808f07cc558eecbf7572e553a1bf0be9fa3b7cecf3668b819b4c0f929d982e4311dd96d65845bbcc1d8b2330956f2cb019d6b6fa3fb3379a6d3bc1b97780b2ca560fbfc3d4d01ffb998eb9596da3a68ce4a5d821612755d1df9e768e8a79b7b7f48e908eee5821d62ef4ccdd1a8f10a93514aca4ab17cf337a65b54bd47dc5539ca2d31c3c63082c10a35fb8d963a15567b530deaa17861d29064c1d4d06833532f664a327597a1317e19ed3f6f3f7910d7d5e0d76314e6809330f11839c4c73e915ef2d553ba0c69133385c33047fafe48619c4505e35b8ad77ddb5086ce9ed007c1434ad72dc67c8c1b0ea36fe8f7d8317e8a1816339edfaebb477fbe92b54a7cba5393db93db3a383eed54ec1b56301f2b5b0f392080eeb8ae2dac95bd5d678ec0f1522bd5f2f5345106419ac7da2169f2806ea07152bfb24872ad3b6e5263d69fbf2093d53988eb726153a6d0f92782bad0e4dc90ce248c9df9178ff741d17ee79341bd8d3e3adc1b75f6b2b263c6fa074cef926fd1fe9973db5cd02ea8ad350efd283ffc4194c9ef1b7236f67a86d7d359ee9ea0e32906a096962b91de2f87ade4134d6d490c302b2c58bd834c07bbcfff000bef44900cad9f9b5b1bc5c32111aa4c39492ee6697fd69e50b25a62f2eb45cc0a9a456e6e5013cc6b28a643cee97c431a1b6c1475c27d107091232aa467e1516ebbb425a3ab5c2434d66e02b97edf653b48d28047bd5f91c811b770500c060d4031187ab7572f64fe81bfd92f3850b1470d87955b90ac6845eadafdb4bf7270050467e102c24e82a6017446f288eb6182026b48d3dd5e0aa6e2241c70b42c007d759727eca77cb8b133433062cf7b50205f3c3a5e70a29f84c906d8859890a4a48eb72956dbe758624e470d7710980bc23a32f9f5970d933b355ba4a3af8f7ae3543a070d8e6dc6d1b1f9a01a00c91a455c2483b14e74b2ff0bd8517c141f38f05f53697d8977a96448e76a1c953e22470cfcfba9b7ba665316b164816b7e38755637c24047254508b3f1a407f829696bf46e9ba62ca184af36c11072b7ac3c7211bb8bfe54782ff5d45b3b9e6887b23819fe307cd63dd68c551e0b6d34dfdcb7913455a9974a67859851d8e26ec4fb47755046d9f1233ddec421fe03bdf2a8b45b9557917d9ed38c6b90f1b2f71a4f7de72488b34e717196d4162a6b7bdf37221ed62770f533aec2f80e1448f56bd4d0185481a8c65706ff4718ab2a0b4d5e6220d4f65429f1e2840615a69c891c6cc0b3dbd78530c7e371a30208386db078a39a040163032bc4a7b38009fc9a4d6da5a1836287ef01e0309732ea4212bbcc9aff1a33176092456f6121ac1e4a7cb07d596cf6261b44887b78b74dc28bb10edbc8f3d3882087cadffa29a029822a8441285af8aff5558df6b3723487fdc860afedf28c0ab9693e48555583cc6d91c8b10ab1f7432f6d6769c4e2a1bcfb27931a82522a9711652c16acc99f8a455faa4e25baa9830ad9cf32d1974b43ac7850ec9e38d175f640148cffba5d44b2aadbc1b906a3c18391c8207f79928165d41635a9ae1909bb09672ae2979ca4932e6a20468b5a54e08251b32574d4035ec03646cbb53ec182c8424b48c8870b3ac66b21588afa8b0c7b16d45338ab4406aae16a0a0e702c57cd740d9c94711153f80c16367a40dcda91a6d783b6950fd2b4d7468310fd4f1ddb1f3139669b8d2aea12f909e296ca69895417d86cb450d73cba1a4f6b80ad2cd1b7e64a0a96b3e895c46152e4dd6757f2c4bdefa4c86eaec4445cbc146da4c3d2f026c5890880754dd85fbf95fe6d529b5b771774f2e13f727452cedd1a59286dda6a41e11288befdf4f8af6586295bf435a542dfb4bc3e434283533a72dc12d8bae10d7df5ef8d7ea5063549d19209088701d16c1b19ff77e5b210cd2903074a1d1013434517d53aa7b093fe6d9ec0fb0110f86824688a4f894348a58cebb2685cf59e70f85b4c9e74cec1b7dfb4fc9d5015eb1c8ece43e03e6f28a54ea404431baef7bc3e40e759083d76c6ee3eff3a11aed1c173c55c60668c6c742ee4d42573ac3b28a65c47a4b05db384bc4f223d01e5b3cd72803b37056efcaa309d04c8b99db85ef5c0733a4a2aedd3719d4833f2351a86a38dc2cfcf3f0232fcc124c79af587ecf03914d44965d85f9b50d258b68443e3ca0f01a5aee1c050cd8ce280b7c0a880180d724f7147a2e266b6dc1be06c530f41088c298690f08e62376dd25ee1f3325d74220723a1688c63bf60d25e329e167c0567836c3bd9833292cd3828b291fb8b1dad918db5bef8a955721fea4d74ff525e3b2a4caca7c213b0531a534c678ed3e79a8278b957af6ff570156f13b4603483de4bf554b033259aa153b9d05d74c02a39453bc82480d545db1901b2e14f3a2ad8e82e0156c0b8a936ec4a74e55b8c2450f3dfa94b53faa4545f1eea119379066df3b9ac9c7d978265ab6c38bc25417b7324a94425f4b8663460871e2415f1973daee4e9bdcb5e5f59fe8a4e9deb3932df05ef480b179aa6418d8052e173255501dd142dbef05ff965199090f13d83924848804123a908e4f5e490b5336aad3536c22ae2f5950f970eb38c630c3447a1de6388bf227ce0c0cbdebd728e9926299e1c59cceccb3606b92030f0f1c26ed532d483debd24c587a6d1cf0c3e20a662b0b5d63b157fa25165b3edeacc6e1c9e41bbc6e523c45a8cbdd25fba270fe2250f12de045cccd440c9a828bd595804b04d4018a13deb2fef7cdcd8aea721ab3c486ca041c0e9f54152aece3515708759acf384bf76fc54a17fd0523ca398962dfe3c9ac31d4fd641919b03e1a4c168b203ad43694330fb557b8a901846d77d4809607701d6e3ba543bfd96c3dfaa2e72075ad4f1d75ffe606c5efb25837ffa86db19e8ec640869a17db92e87583277a11993a2985b26d1c8cfedf54eda025edffdd90eec1500c8dd7c1cc2a5563cc6aab81a0c8c55ca44895e7934215c45b8a33c4489d22ccd8069c5fea2406239acb76c9315110b6406f7bf9a71c6550ba65dd9dacf1cdcf8bf421c6af976f4000a839f530de100f2b28306b7be5186456aab10245f6b9ecdf538f9cba02c5b35de75f01984310a634019469eb149d42987c744ae1d0f1ff0bc075e39168c3a50988cf62f2b1211f9435b5f89bf817c7d7901a3f391802e0cacb9fdcb9c09a238629b075d8b17de014b36e2c7bdebd94e43ded37d5863125d67a2f09a10aa3a3b53f2d680906def9337d1ac5890982a6114f1f232a51c18515dd168846103dce8139457eba7e6290dbf2cff3c2cb482596e3a5830e367c16585bafc6437ffaf08ee532402aa03872c685cab4f92257602796b4839f73eff27e7b3fa504958e1b8998f489bc44e8785797e83e49b04d7b0eb8655aefc7ac2ceee587d9a7044877bc358f5e5f09218a1fc8e51eb77cf2ed503c2372abc6cbb2e9feb98e7f9a9d6eed0907ebae205793355ceb089fd0de25efc69bc44d6e692444eaca55cbf2e33ecb7e15f9b4b75c24411bc19696c6aca6be1f06b74c107d7176379161b8dc20cc5de8ea35f4c5851e111be824a656bfedd5ec23cf1fa815672c9407a6187d301767d35373937f94594d5fb650fd4da70096be9b9902886d28da2f4f14cd40ae23bcf3942c9db05d164c74aacd2637963bc9f6005b99c7328b9d67b4fcf51827c54d95a5416215f78ef5bf816d6a60fa62f2a33e3b0e9c38af75a2197a267f2ef655381993c057d7950ea338d2ce3411cb0a56884ccf3f21d8892481a0bdec739f9409d3e5baffcb5688f97fbb802e67202d844a386651e48d01c30fe9149dd3864e81ca7c03b83a6e2b8f2dbf828c303e1d32ca941b4494f9ed184a45cf99d209deab29af825da6a1a6c1a77050a71f43ac7c1e73cd51fd25fee967057d6cd08cdffbfe2ef1f5e9dfb0349af16946d90b94d984f16f0335a65b1297a8c5c06096e297c12aa3ae6e3b2364dd8427c6ce5dfb181aa31f5e3bdf96d7de3430bde3bac65496d89731edf69498eb98a1c46cac8e5625adee1e1bc27bc9055e940c1eb1dde9b6fbfcd58da8b0560b1b804b22c4441660d5056e9cbf2f31d782f7dc0212be251dac48153ed4b36fed58b4fb0fbfdf095dafd4cba937898e895dfc30ae8e3bdf156b637c430ac47c5dfc9319b8bb895941537ad620c5921e52511159b5576b66ef719f4f5a39b8c40f15b687c51bc535e34a5a7a0493e3f97f25f79689be6267f5acf00bccce0a1f80dccd6fa69488ab3300fab40ede3d5b7b70292f25e1f2b9732b8cc02f1f5d97502e5e5255845da512941374e524cd95a59cd4a854f508be96ccf530f00b549ea02b42e25791afe27990fac69a6aac9c3d1987cb358b88b6fe143ead144abda4643f2fdcabf3b8d9bc5c15a96960d021a41d9334b205fd85d547350d0f6c37551d18991d40230dd0fdc4a251df76f73c711a26b7f1d56dcc95e7ec5404ca5a5515952f0961ccdd1b10406925048021648f47bfcbaebc787356a72ef62918a0228f5f47d2c000ba66262abf18c35cd6f6811baff71882eb8402d0cbcc27163d98341f2d1bc0048162ce849d59ead023544ca95adddf96394a86bbb8d4cd1c02e5dae6cfc3c29f9b3a2ff2983f9a7afdd02eafa68d51fd79280d7c55c03f63a0a09ecb2206573fee5d29e3a012957c061be0740d424362bd86c70662a21e3850f8421cbbb31f1dacc84240435d440f7652ce653710a0759fa0bc9999d857240fa969e097885e58f77236ae935aa4de5d43fd7100ef65a6101402007c3b6555accab545696cd6810b1f555af4186b1bf1162f04607e6b1551bb5c69673a263df01d9c4385d83bc82b2c7526d847f9a07d282f87a163ada49362f1548d60c46db5fecd5213213efa7c2c1602f1d5d13f5c076f52f0847fb271cd1fd35d15933d16fa1449554d0d201b900f930b9695c515bd99ffefe6222f2791c9ba204c4d8fe06285d06a206bc88e6fcd056e2801f0ab2e9d4bb38571e4f6553c028503d7324510e9f9b533a2e20c4bb2bfdb9507a6b2cb728bc7071b919ca5b779d95f6cc78eb7672df65c5a08f227325d9b1f9f3f92c5a05e5747858acc0ed3df2b514043f6ccbb2e102843943d5a086141dad7cb4513038aded86e61ce22f9d04da650b997f6bcf1a2ec1602d3f0702e8ed802c4e6505234303f43084a85593319140b6afbe4e0b70fd7dfe7bc530718a51bd8065b80566e53190457d8f070455b41d40ce1fa96b4898ebdfd0f489b0af398cec0707e66bb6b98572196c45979f1311f79b5cee52cfc75f1ca3559c3451959ffb8aea34449da25d3bfc06ed60fa646e61ef6ea51a50c9a73abdc5330310b3838196c639db3f65a31c84efce09be6e3131e22f9466f59e9e6003f9e6a1319612943188b6890c33938379f9462cd4a88013949ac313aa1b76100bf429b0bf8e02731d50d4706fac1414f89bb6915a1b4a359b1724aca566788e2c9d5278e24cecf70bb9da59d337fc9740d6dcedf9f574b3afdc9badbad536658d0049241625151894c0e36a2a01c25f3de2fe680eb741d8563e159ce81fd74d27b98fcea7ceacd148a96493d49d20392c14afd0d287989e6261849dadb17ae5ee17332c307dff85a3854527370906f630698dc05097af459707347e687c081bc9da5faae994e1a41792f545f66d4fa4c1bc7af81ce2b472adb513c9df015705a8c8c7735bb67190c8d3d09a5bdfca408704c627bdb02d2624ac62e674c54d0bd9dcaf11dceb74d2fb4e16fb061422565be8381861701a45e3b5e50b3e13bf3902098be04f0955b8a45ecad0dfc0540fb536ac350dbd80d2fe68a27f724af5d3838a4749571a9c2de46232921a6da73de074afd401349204254680f807ee7b91fc11cee84df0370c9d1f51d2d54112531c6527a7b322dac25814040567a25d0a5e29335e5c07fcb4e9d09c5d16564841e2bdf71a12860b3144de89c420a48319033184399da1fd22a137d6243acc9c1d3fc1c9fd8e7223e8192d5c06e0548ab9a2719a9f28f73d003aa468d375bb4e43439220b361c00990059739c4401a7151db9aedb4933c2c6f44f576a97a8f7549b82c65c254d44f7a65886b8420b67182ddbda05681f2b22fb641f88dae8524446fcdefa922f1ac3d7e7790c13414fb735d6f09ca016e091b7d89495ae180bb54ec4e27214dd306fcae785f94e9d5e7f2d9727832d0bae95d318e2be5b23ee472753a58623e93baa62bf98eb1b1f5a76981b840cc50784e98c4be8bafbf7a11fb871372a44b5711a552fff6ef83bf9e9e6f4fe6ad5a762cda1f9dc2d98f082711c9dab680a51b493e5de2dc576471c239d2299e2c777f34f7d8bc17a0c90379c2b347a7799802fb035e5de61d537cfddd3441f3fad0ff914ff637cc84076674f7759973f0b2a12f82d3ea7ab47d0f34fadccee4b7b3fe6ce61959d9d011747049b75e5a31ff30b5dfdfa267c618a145ebeeecabf320b04351d8bc491b196b4fb52b2a2bd591d2515df86c25e69b844dbfb7148a645b05728edc813193937e200b92e2e9e47b860035d0930f63644f9fb6d2c3e1028add692c6a0807b19bd1f58da901bd195af29487ae8e582807ff25770fc6c5ea33146245bc34146a2cfe7bae1ee3e279ad1e1fc8a6aa2c2ddd3461309cd85908e5ae7ba2ce6f14bf99e423be84787582c9b650c61670d012432186da035cf6cec308c34bb7e7e01d4f08c6a876d0fab1a8b7cc662a4c44527a5674cfbd3e3a26164e68020a4e55f84967f3e09398541729ab5c3b2cc5a6815b03056f25005dc3e3f82616a53962335254f0fbb594f5b3641cd8ff0b82638ab965e407462459d40f6f9d8f1e468f1ee3a7fc008013148a2cd6e964c1660c5c468f08fcfb57a0dacf1cec5616dd51bb78fbeccb8371486ee35da46ff4e7df9908dede1503beaf75a3862b066d28408bfbf73bcc1cb066ddc987939e9b49c5e957086d3b1c3e59c299972bb1392db940c7c7fe613df5170e3d5a587def805feb3ce8ae819559a64bbef4dc32f610d30248a8ece07ebc7b6cf3dfc55dce4f248929239b32bc3deef909a101f78ca3dba4f926b4426973a5183ff032518844c951057185c3c31dafcb6497a6a47c3423ed5adbc0176f7423b42f7c217b77a5c4954bd451f5cffb024c76b5ceb4d9e1177cab631a1fa8a8fbfdf4ef8dbaf7cd3c4e7e454a1b3a47ad2e06e9dd007c62fa7f7b8b4cd4558cceedaa2e496bcebb828dd0fb46565144685a0da76b7decfd576c89edd5af4f42c41c9990b52fc96d5324325cc49ddabee0a88392a6870f1dd994c01497e02b97516712236188717133b7bfdc7041bf2e6ce650cf57c2de61e0406bbfdd3acbc91ad1dc28b1136c3003856996ab949f08cd6bf922e440f97db594023f08d412a0adec01fc074cf330414db9fe52d160cf6e8b88ef08dfe61c15bb2e955710c38eac28c4b5abfb56fbb212dea88819f7be9990ecab2495d683d64d41241a31b9402e7fbad15d3e78431f45a339f96b47dc1ae2470cde253c4d1938edf37d96f185a8f7dc81cd5494c220baa29d8b25683d7aa2053eeadd2893fe523261a3b1ce4c5b53b9569f79a05a41ccd00c7a75e546f181b3bc858586c7e4d1811f79f89df28d3ccf6774f44fad8468563697e6a8545ede038c7fb1ee14f38dd7f45af5ca586bfdbff1224dffffd5bb48f75658b5096a3408de9102a85af204611411101a10cc5ed95cc65306f6f79312be05e7a3a550586bb361fe5238694bed3fb513d1aa448ad1f99f9ae72666654fa6c578739653029d7356571ee9dac9881bcd43280da3fb7571a7470ebeb296a03b91a23e2acb750eab0973d8a6e4d88e64f8ca1aff7576e9f9f43e4616ef7160a488c1c808114232d6e9bb1c50366af78be551ad625856191059a29eaf0d602412198eb7ed988da74f5ba52f4d59c498217e85b27e90d95c08b3770611be82c9d29029d320618356a7e4c842996b902a6b1584c4ab97816d04b69fcd6698f318c03c5efe173d516c84393d353702e9023cf35d2378cf659db34cf31acbd244da2e562d13fb88c5e7744b4facff18b5a5fb240600136765ea2b439994e12e5e482666c1db61296518c51ee74ce908652d2050b5643a35df64fbea9f00b139c6b04d9c8b784a767ad0c2c3bf5c0769ec46b8f7be1b56afe45fd83665959e08b009e88619e8738494fa3697905f0e3908278573ffd6742650760f3fc303be68ce0cca205d487714d59e30765e63af6d5d55e0b56b94eb407ff63521bcd20bf00d0cefa0a2c2b148d5be0b77dc7c971b0e5257b3937ab721711d374d08c2beed10a85f5a0d06e282400543629b211a18ae0f366adbb5c28b853b5dc55f915cfd00e502e4b135f37aef9d45e30a052df84c41b0744a63cec71c8305f0bb66847e0298471d85df670f62fda18d09abbdbdb2150ec3addea95c53588a82ffd7675c63f8d73b2463741993efa56fb4af8137e83659ce9739ba9a30ffc460ea1150ca4472a5e56dd3392a91e7a9ca3303a4461019613c23a3811886895eed90177f81e426131adcdbcd27c14ebb8956bceb6f9c4d4a2775005a9c205aefd7f356a616f9034170f027acd2dfe6b0e3f108489ef602359b804ca760b0a57ee4a015491978b5a5e3cb113ee999ef47b19725681dfb870b2cf3ab7fe20b90e899153a5da320695057b750c888ebf9ff0eaecd08bd0a327c2adc8b37c0545966d67a99dd2ca1403a9620993a7d944c28797cacce1b5d8b4973fba8463987e6cbe6b4b5ee633ef0591619808665cd6c209abe839127e118270db8c880d6865928164fa5b2f907ca78ae6c7129cd0558e6888b97eeec50b45daf80065b464b41310b5e5be34e88d9f9d88d462b88d3ef68f38d6051dedbf0d83ff58b95c0a4ecbf431d413dc567629ca4f6f0dfd3baacb993e3366b8746c3238b61ee53d636a04f0bc0a3917d2702dfa5db5e33cfb9bc96ecf16953e216ce23450ab4d7d9a6b9cb01c27ee73a12c7ce5fabb5d2191b12065f29b43482e2059931fc0e2919dfba4de2b95c8afef0103b5c115b20513a5ba6297fc7a5b53b81afdf454f11acbd8153c683668c1e3b86ded5e6368ea3c2f6d467dbaa73e9c7051c390342bec3d81acb4456e9d52f9553d70c77855d26414a58f6c8a1ef21fd2f128bea6ff0f444ee3d1c09d5eae4377c638b5f29a2a5f6747c2b1d0a14f26b447979a0fd66a15be8481e843894431768caf6e74e12498ef0b9056b904d2de4a77cb8e4753bb8ab7aa83c90b0c347894d8bf48805a24837aa3dff89a4aa9655ce91a32788a7e84d42dbfbb9fe2fc73c2a66a356be5423b03c12aa05fe3dd8c0869672a880fd1db0ef39a2327228e629e54de43a02160cc0be82fa95afd20beca1b87203110425c9ba9fbed8d12d8477793b20c8361b50294098f5a458d123df17a45cb3699528a3e0cb550132f3fdd38749acd837be25c1a6b699c1c6aac5424ffc2e92df501086389844c01285b7915bea8970e6c0a31d5ce22016b7e5bd4c6f813156e8340b8b31ca5b3254f4ab95d5caa8fe4c23df6a9fc83158fa9236ba21ed92c31ecb12babe497efb39a2e4dd9cd0ffee87cf0be889e486d49ed263384a5d83e29fd969d7138a9a230f24ba32e0b6d2a6854a00f4f9afd1dc3813ad9829f8d5f8e322607e6d2a5e4b01ebbf6b5b8454ab54fa2f7db3d50d7fa4c36c50cdf4764bbf83183622c6306ddcbb41491b392cf6cb117cc5f2f6ad73de5aaaaa957472459ff5877e6ab0f8906f48e7390bf90e70b2b0cf56de4b4812213a763323d03c5dd9a52477f14c4308942382ed9e6fe418339c355ee9ddef7c16a377048639e0d53f28b7d6370fc4cbd176f7fd0ba62363ee697ec36e04568fd60f226642127555ec2070b8d0967e8cc6cd92954ddcd51aa320a80b3e91d0d9b0dfde41eabdf8a677d8b9ec375055a811ef3a9010295edf54274a83f694232e132ce899fa8d8e3e69ec328905c2a88a3b82e57d0bcb0c8ba16f5edd7c60e5ed9f975913859e9041ffee165a3f386ae6e7111e8d93770c6cd1fd741b6020232f3bea2c51635d075bc18c327b1ed5c1d02e2aa86aecb1a4a6963a6e0de7022160d9917c82c31392e81e7e1f3bef148cb017a752e95bbe01ab77b0b626b81236b218d8e5e32e161d729693da9d6654c07073e8d4ef84a04c638ad78a04dc6fcea18501abbb2e631a6901c302deed5b4ad3ebdcc18f11792afe255f3b16caff1c02b1ef5003884607e8e1219749f4c6ec94c6275c91c0623e96168e88f1eb56f3d474ea5412ac0db528616a3da558ca21d0db6b798c56feaba31f5e0f7e704444acc7ebc969c469a5efd2e2ceee8d99294ca8cbe1d343d4cc3103145c23d15a94d81e655588291a00bfa9a2e2fc900da8b7e125f6400ed700e881b239d421289c2a5b7fc5bfa9de294a334536f8038a109f00a334ff52ff3dad2ce55b644606692a5a900a5d6515d0032d3916121b13e463ed2a3f70c4c631929a59f8610c6195f41098ee6e6c525d2aec91e510e80c9b5b2643c4ca4fe01db78fd79a7ad28a45bc78e8cfa16614e6e8354905c4e0866c18cdb882324b8ea6a89338de3397f5e102e32ecd7c14a6e7f49426439230031c456201468264d59dfcfbd5585004dc86c557f701361b38dcc8bd68414746bb54e41beaeb2a05de9edd7ebe87d23f2946db214570addf925ebb22911363c76f50aee5e8de5ceb2e71c799fc6dc4ff9ee96fd5664cb368c2a2279e493f0b36bbd1e2cde368f882e81dc8b864ec3be0cca5aba5f9a42e38d41879b8df4c33df488e7ecb0b935078963b84af2fb3a5aa1f66b16b94ccba1a40c97650a79c7d94ab640c566f59464b437562e5004a524cfda08091e2d0acdaf3a7e8151419521844d9d1be4a5f865f4a4ae329204cabb2cb9a17a2894f03ef24c28a0b7bd0433455f73758247b9680e760993aa3073f415699c4ed64a096e233f83fa835f11345430a6affe0aee8fa824aba3de464863f719e93f7fde67447b6f3acf7639e510a7693eda1be4f2962159d1cd5de4cebc18d3faadd98a921562163ab87ae7a0de9dd3de84edb7843a1c5f0c7edae1ad95c13de71de2903c11290f54fc51b10a1d4d9da9b50e6b547ed0e281a9bf94dbe8cb397c228a4c6e1525ef9b641556339fc5874674c8ba6eb0332fdc5b313ac3372055ecaa07bfcc531d0dc97d2e1b8647afb6ad77356d9b0d2f13a8414a4a9467df90160c419b500a1e81c7cfee51f8f887bd2d1386603cdbd914d590f0bccfcba8bffce4b07e69bc3e85a599f63b3f8a7c42fe98e1b1c2709b0919988bd7a3a30dd0a7aede457407e60933a650257eb3a478a426baf4616a9beafd65346a80624a308084262c649b320070f584bdc334eedc68ab08fb3b0ae972b29b72d9a2d75eda5199a2e6032b9b7173fe62bb53cf0ae4bde9e735d753748b0fea4c0d357155f52ba2aa5edd8c5f4fb9ba605fb42a95bddde1b988827344079cd076545795db2097b6464aec7f5fb48132636a2dbc90c5e8e89678f1f555243904436d00c7fc5f261b54ef0f01772558fee8685074a19e1779e054b44fa46f22ca8d22e88d0bb631e0f18631080fcc4e7c9ad6787f70b09b6f18e8019d8151abd27f1b9a1dbb6e9b3e8f594ebf2f9635f138ffe7f40406093605c081772860e3bf4e2a2639b927f467990e5026f8d39fac0dd65812bbc0731cf4f91024618b8acbf15778054bfa329b199b2aa786f47084b84ade2d2bca43b381a43b3adb9775cb815161531a20f586e6400b25c5e1930bbe06f9e8764a5467b3a422576d51237f0abb38b51e45cfc58045979c042ec06ab9eba697cd85eb186530df600211264aa8ed6944b9d1b0cfbe95a28dcbce65cc4c8a65d356e75a773f203cfe204dd87aac321a295f33d39b37ceac2510c6dcf73743b29d6b2daad27a1d82a101287f86669ac27b24a9797390e4ecf1c06cfc0664c9b97c7a6207d1f16779e69faf8398af1d33b443434a672e8999f4f2a85ae464aa9b76d6fb1f5491b198ca988159d90d95e6b4df652f7ecb7ab7a61098b4cb9f573fc77b2d5d3374c15b2457b5924a4a55a28efc455b6e3e990c5e28591673432155e873da39c82a699817284a04f0aa4ff343d032aa4d40eac9bc126c3b75f5e16ac63ca3c0f601c630aa60c47093eb0923e9a7089f225001bfed9c66244fdfcfc97f1f851bd53ceb31b99adb69a63775497b715b0485fe0c1f1e792accc6c884fbb4c64c856ea283f731d3ed5d8bf297a476562bc3db0e34fc44c841e51a15f7b4978793b62076e8d31c5dfed113e83fa2b5481f9e4dc35da9445ed0c958cc93984c1877e441c70d9f9f847f48171a16534ef1a2134dd43703598c0d6f23cb93b4f5efd7602dcbe3792dd26a85d4dcb8cc4314d6eac867838821c3ee5a2bc73d7b44fb796c1d7d296dbe9ef7902d52b1d4f6788e65501eab7315db3dbb921f65d9ebe4a832e911d7959341bd6513d90a84dab38c596d50db373dd0ff402946b06c0e755b24b772060f906d5238693ab5e586310de2c308f86a844d9896e9c88bccfd5b2164c81bf95fa946e8859f26de8d3c700c7482458275741ee0a60a2a3bdf6eaf34d85a3fe4c0b0f1b516529ee5f7adae3e5fe9f1ac181cb6baacf0ef471d53af899eb9e7e62f20bbad970246a8e4fadd60c2f92e068aecb3ac85be6346bab129f0a556fad75391f02af2d9c2c61d0fe7c1e88e29ff546b99ba729aad64b803d0f99d345fc643bd1fb9bf055ddf6c2a071b9146b4c37fc016b80c54a602a008646281c891c0b6d24f8fbca456f7ee704624801ce73609ce692d4431365159bc7b0b0143b899b88f4e87801aff0c562678ed087702eb8fcfe94a23a39e9ba0fcc1f454e2f8f2b820a9a42478b7ec898a63a315ce64b1b790aa320afe24a7b4d0419c3c7c857bf35ba752a5908e51f0995d07a9601eb54f73a7c0e58522c2d6fa447178e8468270d3fbc99abfb7ffc9602b4d3ca7a021dcba1d1ec7ea2b5bc64cf6a58abc4fe82c5bdfb0e5991fa43fad84ac8800449754a98536619020a4b8037ae8cd16849d583921867667b1a3ea3dc70121006bdefe4fc8a4b07521d1cedd38b7f2dc665daa1f44cb0c5b49cd266eb34b40fd2f10442175fdf59ef4719419c87be85d8a34a06d8c983e5b904d8ada20cc10b3c241b7601c25bff1103759945096d10146035490f0738a6e622a0ba84087a4d4d020c000d1af03160d7aa6eded3496d3bc735c935605b9f095c0879aeb470fc2dd52d36bc3fdd8393896d7a4609e25e27b015cedab17295df0f558e39df469708896e25d4fbe12e0dc1bdb60c9f04d5f955ac798c5d5e6cb3f321c03bc3192daefcfc057a4755e81b99c13566f812945b452a543bf78e550ee2a1b5e6f763c4269fcf8fb1487eaa02e21bbc310216c17ce6b58967d95c33d109137def8de7f1b0f7763891a3f9b4eb6d466fb30fef508665c9856c6f836b487e2740e5fc8cade527100a32be7d2034940576fd92e75226f046ab5359871d926fd19335a8a44961dcc576370f38b947324b954fb4ee852770f3d9f88d1a02f44f00ab58379e16fbe6e8013131c3bda5cf0963bb3d6415b8a2923505c00ef828176a8a18a90a45b25d6e3cf22cf49ae4f4e2c24597df5e58c4912efbe290c0d64a67e6e3e24a80f20f7b509c3e3914b2f07098b104b282ac73e6cdc46410787886637842fb96b5f83b501da5aad6dc5336de80720be1a83a98fd668e4f2501b05c5075999d167ea60f7cea6fc55263e5c0b663538d093e2fa288a411865d692821d16a014f3cbb4ffbe8e14140c574d97ca32d78c31e9556e024ae6a785f6701150e58c901912224e98ae44f033fdc5c4af2a77d0f67aa13a1543a0b0bddc57e61924eb61983f420a76d7ecf931f3ffe62fd12afbd3a81c07ba7cd80c7deaba9286aa0ef214a9005276d3b90af21649c656f297b65ac1d42ba17044bf5ebef31f4c619273d36dd84af847a21dd8951f7d658bdf284dc73df80a6319176008e6b0bd0eb870569678b398e8a6bb68d49963410d2ad16a854409ee317b18517de0a333d257aaf6e61ce7e09f341044e0d356b039102d7f4f6a8385f425a9c333a357608eddb0f9b935ad5e158fbed64bb1bc9d0f9f8b754a7c64d1ec3fc4bb34f0f0a801b8f2e175113f4570eb03d3bf9dab914c424b35fa712878c5dcf49082d3906bb6eea7073bd58151af2cfd164db5591c59e2d527c1f35ba3d1c41d1472c004f3c1e5db230ac1c50d75f2855ae80230c8a3e061174e335601acebd80fbe5e33367e5e74e2790a184ab7347a0079424d3e7d8f1e20edc591572d8c5c9990a29107ea533b525a6a26f306fa78b51b6299a47718d101c2256b2af2eb49bb0a2abf25659ade82863cb8fd095046db73754f4a4f6c8f5cb92467ed58f1e3d207f6dfabd571c6bf1d31a27e4658a7a427a14876013cbace51e300c17dbd8349cab925ec1381556ad1d2164fef2394ed346b1c701ec5fca8df9dd2c42054c036742ab38417c6c8ac75d2f35cfd3b8836411fccee886476374c9672d569886c68cb7d9ea98e5c6b788030fdb8222591121803ef98984c9172dae3ebcf407fc805cfbdd580f24ef72a6376f468159f73b609e107702a20d1932762b90c507cf0e7f0bb7eb6fd6132c8ea5eb81300acc8d1bb0a132a45f3e2bb36ca238a5e2f019348f8731a1f6bdce0202808e256aa27dbed93e13759b47b1981af59a40e3b3ff67586f56bb4cc4e0f7b020faa8cebcf620045b0efaf17a164ca10a800cecea678f8033b509e64ff1704ef68dd8b010620410969d5456f835a8c963794c3e76e2d38103a35846235333537d435d60d9718d79ac16b360133a78a0142290538e4dd0ede346a149ba5b7e8a2e4ccb370f772b07448184ced4859f41049df6c255d5f7df6aeb00b48410bbb34b3855981d764acc9db83fc4409c5b1fdd9cfaffb41d9cf1430f2b93635d1a824bce371aab3fd96da9aa50f9af8a4e2bdf09e720e5ec8cbe297d9dc4d9d8e000ec74744ddb81436f37ea7e853c5fa434fb8e342e4543e3f5786799973f009b3edf075e99d04b013c11860bc42a2638bafc54b651f31a89e9ea0addceac8c54986878fa154a019d73533a97594c43dff0cbf6ecd292902519cef563d1e84658f1e3e39a77d8846d37f1b77698c33af17cae02de21372f9355da35d7760cb622ab0d8e21e7925ae3bcf9330c81a7dde2d2aa1d45e501d747f700f8c68e7b8bf3293d3cc6209dca4ec3e0adbfc7cb1e6aab4e8a2110cca9a4b779fe40a426de1827c8c5f63f92f7215d45189409abbe702dbd2b92604c05b237b4e4aa582fafe77033c5e764dd31fcaad1e20c009e1e8ca0a93853d211e6499afa025d037991f496b35dff6868e59755cc24f37becf4ada3d326fc664434f6c1af6a21a5c75ade99cbfa4f380d1155d9f7280627e0f37f6b23f405f6ffd3537a12f4379d1bce082ae1bbd0c18ba8096c46f33ba58c1a1e6bcc304f73e8534165816ec22029cb8ff38d8e4b9a4ffc9480f8a5ffb3d51b1bbea2c7295587c9295f94a9b3630b30d5e69c317a5c72f66680ec0789c1fe16feec7fec81b3335aa8fb97036e5def0d5218683261e2e1e8f40f5cc5a5c2cfe0246750d304144853bb781d3ced9322996c5cd375b8349d37a37bf16d91ae2e9b7f8c4f7e1d638cb6511341cc1f68386cbb6c4143c3d51c02b1c216f52f6f8cbb3978ccc556a0efba170240a6e5e25db6516e74c3e42a071c319d872bae3b1bc3e44b37e54d2fe070c78b22f24b6734e1aae34aa00f5572b2abb86a553b9d1bfb5d81e8a80be3e5d5992473f5a9496561333dcd6550c7670f4e0ec17d20d414f108f9d18474b62d9eab279fc059f084f4334a67a06dcd3d0646b94b3588f9c6deb372d7113066bd0a606d3e5cbd1906a58c03591e86bb53718cc4aed0b36d8071e413d1840bb1d20f6e71277717e93c399d715a41aa44083c826047285e7ce7a624035a35e53d6dab8aac7f4d3028db02b47bc0536c76d44a483021f89015b37e1939110457fc3b49be3d072e5c9fdf9de737122bffc54c4cd4c76e0ffdf21c2dbcd1b9be63909ff2e7735efc257fddf574c12bffee6e2d0fa738a1ddcf14c74fc81ed8b9187c3b12feaf581d2cf972955a48573afd222109e3e201077b01d7f0c0f7baa52978344d29fbf058829be1444000a9538febd744c6717ac8555fd427ff5d7d4c3c788fc5c41e746857e4982bf89760601ce773751a25c781ac3f07998ab1f0fe427beb18449fc64a5d3fa9d7d5124b8314b6dea5fc792ebc85440ac1a2e360915b19e9ec54888bbdda9de3b5e0677a4a71e3b74ac76336e8bc51fd04ed05dc42d8a1bfaf5d536ab4f2cd7aa6555684bf797a28bae9c093a256f41d5660b95b322214f6cbb8afe453d091a1e6e33514a265d5bcb7c1519b7b82d927b666fbabc07d75e5f3cad63341b5cb0ce7000d6dba62c6c4b6167d285492dff9b9cdbbcca0137607757c7c03ccf90a74728428016ced95f2bbe6d4f730e1d5d4059bfc61ce46646cf4b885998ffacd3ac191bbcb44cfdbfbbae53748d71beb028812bf240e4b0e4af9c3b31122c722f92622f9b1c30006208791f2bcf7bf344ac40d0a6f0687967b4206ab9173d71ae480dcd6e06fa685c96d67e34946ad80bec83f66d7d5f0d8a4e21ac9ece4e2f0a33731ef8f9149bf9c726573729afec6abcc615fa9f610afeebfbf33de240330c5480ea27cc144ba6d75017e11be722b5988e25643d777dbb937d8842a0b7f617102c0ab94f3d56581fa777b8336a020820edf86d27f537412d9feb29deb16a2371e90b91038ccca8d4ef6078ae5e246347655727a3dde0593bc0a580e81a4849a1aaa645c20fadb7dd524f8dc6133b4174a48c2b750203a82e19194c41d6dbb32e6d9cd68ee88c1bd9e38918b717c6206955d4dfd4718317fef354bbb9f8209f0751547731f7b90ee0ce0b08f2826d228fa39df21ee8ab88597d94ac1f23ffce51eb3e94373e15d66dee896b6148014e4b29f9e6de9fdf0ccee6d9c036c3db79295c37ea2bf16f740fa74a79a4910cbf43be979ed327b41800b7eee6c1a02834fc45971c8e24597a91a361dab42a2df3e357e49824520f56f0b9198d3d1bbc7a166d3b093dbdf8199ac95c9531ee83256420753cc5a9d0095330f6bb9b6fd10cd0b9c949dcd736c10eba1da937bdaa6e8a6c015cafbed131646df221288e9afa9db6c3942fa90d26d1b6e57e199cf566d89cbd38bae8b9b694999ddab55a26411e22dc5f07ec17d5753a023dd6bebe23a0398c8cb961f5576e99cc732fa8bcfbcb17f27212169c7b4897ededdb127e65ffdb1e2f0fdb5163358a98111e3ad31258fea59678338f0df433e5833d4a7fec3acccf1eada88868a07929cee71d8000815ebecaa2cc28fa20f75e565dd3590dbe8a646c24db3bdcc71f70c869556d29e7814c72f4de1dedd3852c8d176c812947e45ab9c1a9b39de189817ad7225e4021b09ae55e876f5a108e1cfaf633a03bf756c92facedba2263be64c32a085372bf3a16efc5f936f3a2db82fe4303fd9efd2956fc13e631a9582b0fab5645d1c78661993e6c68812d9168d4d059482f41df42ac2fd989dffde86eae76c1de1caa73323a9d5b574d4efbb53ae33eb44ae62fe3174a184c7ca5521b7798a3d8bc93f8d54bb7ef2250b65394071767a18dd84e320cfc94bff7d3574d71e17d77463f8f926b9d99fa6329c8c9ef602709912d0631dca4ea4c16a5756fd97a8d8c3363867845fa454ce796380b72779f887f44e19f47ad488ac843737618e2d772f52b60934b434d0051a977269c504c4d9138f282e25b0909463fc370b6aa0f2d3317c3a9dc1f1cf63a4b06d77e5f4fe21bcc34b9d035cedabc850f01a3cafb120fece7dd572e0545568a749ffb2135dea882dfe42f78f7b5d035cb0ce45a6273e7b5968d1497c35796edd8ddbffb073b4007b12a3e11ff063b8adce0b9ade4bfa27756736cf5996694bb67ac4e49642468ae09b36baf82fdad1c361087e555b7d01c9a6c1faa1d13883120b9d64fbabc4eb98db6a9e447ba5ecfc21174ed2af2a0a28e8ddeae1329b819f9006ddc5bc10c2ce32f2798b8233da724643386249d6ee46d9aea8025f56d0d42bc3c9bfe6c2512f4942907a1a2ce58d4461d6f0a1626429496a52c615d24a78dec03a94c72efbeac130f5b6aa260d4eb266a948160e5ffbd84e1546bd8c42e2369dc921a506b32cd85feccc5892a86326bb579dc09ab6ea0d7bac029b9b7b1ae2a8729b7d86a9fef17eb85075a76eb3e81fc759aada40856e89a19385ae2791b9671fb73cef2a7d58c0c3a9baa7b92d20207d0fc09c3c716e084da017655b56f725ccad24de47a2da3ae9d80f91fc7306e1fafc19aa92fd25d7ecb0b9da96027652b04f096cfda3f5ae607c67b2a74b3d38d97a56899257821db55616e00e9fabea8d907ace42ebf47e6d8f27c8d7825b1a4ae6ae83999a3858d98c36114ec58c7f02be8cc2ffdba97e1ee61dfe6f3e231ff796354461dd08b4c3081a9c55faf491f204464436bf3af7c93c864c6ae6a7f44cfe9f482b72b2fb5a07b4c5a54706cc38d3c0d8fb353a5b092590e25359376c1657b9e093703a8b9377c1bad8d4cd57c3fec46705d1ef6f4020b118323cc10e513b8e97f579d1124b199c1897bbb5a96154b5f6a15c7b8a6b9fa0774f8d05363c5e51427f57378d56f5e3ebe5298dd44564a7dc414f83c2e00e39553238478274637388ab1aeee677e80f9ddfec9141e78e0f644309f0881b64f972fa1384f1ce635bff46e5ba8de505d635593debe85a02886db124f3b96846f79a98b1699bfe1cc587d4775b9dde6b4a9a87625bf79ed3fe00f35d2b2f090038c68422700b2c9535f9f7dc02098cbe203af8e42b7447dbe07264301d3f8cbf177201660bcbdf60eb08d467dfebc61dda2559506225ac1b6fbab4a1fff9f60c59b9bd97601c787a86b2825bcc3625e0d75876fcfc7357f636ac699326b384c0c04284ec85ade3548c8050ae93cc1b94002edb274df6c80b4da9bc8c2edc05ae9172bd817df8b9cde1fdc2a88b8ae20116894f1cfffbad59751265f018e354839999edf9b610d3459cc91179bf98687e46f40979705ca4646e7f6534d2565b9826513780451bc5b7367bf44524041fa4dcf2e797a0ecf9c9697701da95ea5b669ad95ca1e98524d46340719a811dcdaa20152203cc6f4e906eb9195c425408fa5d7804bb765575cd33f2e1fc79e2c95836688acad40a6e05229590e5f561c68953fb2b6059f472e181205298fbb3e158eeb47ea0a658f06abb7365218c1178394ee06e219676f0f8fcc5664a8391fca56ae9addeacf450409df1e753e90c915e6ca522749a206458951535a53d9236ac7b5b03808cf092df695d22747b9f5332810bb4d1ba6e1767d01fd895097d24a0d5f3fafede5b48cbe3947ac7c7a8f8de8803b11e42010655e1f31ba90aa0bb510fcb59fd473f2b9843bee71eea1d8de4d8ab7d50d483539b3940bdd435212e244762ac45503470597671154205a37c4707e3e00438b47914aaf76b7243703c67247024d012bb3f4b3fc599dc8826b3351a0659f1ad42dcc5e7f56eaf72e3892a33b700a5efa7e5e29e8447227f049ae42dea8d5ccfe8aac19bcd1e2a6f9ac2399929bb40221971133b4fa449f2f39b0062314ade8bfc32a2445878c90356503035fac7da10f6ae3a7855647e4a30ce6f40583cc4ecb65a2742d8206d8f99ae2bedfded11579a97fe85ca781c5d0a462c69331e27f1f8a5c1617c72a45003fcb8b08f5ff8e7c0e2223c6e810b19da5832d7fa40c746de75fa30a0187cebaaca93a6336cc4f50540f48fc3a7451967465839c6f6993dc56b8c95ed36ee40b5b1ce367c3bcbdc45a3e65c8e42c49e41ca0777705b2c7362f3726f34f19ef64641cb71522b0ffcb53d02e4cf52b07a36b42ea5a05b9b2e38770a8879ec4c56db7c9a0cd4185571e27e99e6749d82a5bf72f7232fb6ede46abe863156be79312f01513d3e721cdb8c291cd93ac4e12efcf336908c8d5672ebcd48d1e3a7c4ed71e793fe03f90419ac4ce4e7072d657587ef3c0a8cefa5f89bb28ad623aadb1d94ef27059e4a8e2c2916fef6fb34dec43965d420de8762a9fe58779564e3eed324a051cd1e1c9748611861a897cd0c25ef706a6305df1930f6e96a64d592e4d0a959c34caab039294a0c270db0988d54bf33ca655df90cc317735ad9f257eb50b85d1c719bf5a66df661fa3b80bb58ebd6b9db964a9ae83dcac3f7f2e47e773b26e13e2d07992ec38f25cd09c9f3a633e300dde8134facae7bc85b685da1cb95cd6c49f3b13b5a4771a21292097fed58cb2568c8386363bfaa6712dbdb438adb2d564f54c76b7fe2df2dfde7734ff194e31c52a754213ccb733ca2b7532e62abcd41e21a013d5db762b62334f63002d31705f4938a141aebf6305cbac2abe57c3565dce3a071dd5ba675de33aa192984b6a460d06a3a5a18641dfaf480c9e8a5cfa44f7074c96af7f9f75f2cbe6134f530f6cffe795faf311748c204dc10ad983eedde33f3b4281d9ea15ff75b5ee4b013a552ed5b194d28ed38294039b4985b71be1b54cb479d3062e7c779e3c3f3ae08f17895203dad682ef58383c8dd0ac95d5611e77561cf9f9438a9ab7b14f2eb9298729f618829a3654b06758709197a90485b2cd799e8b7f0043489a2c9eda1ec262541a568a2f5028044cb1e1c6ce6afc2f1d3797f842e590e0eca19bd9df174c537552cbf7db9fa94e9aec4441129dde3236c6f1f591d96a0b441adb9ae7538ee517366f6e462087be3b8967b252d4c96fd88d2bae0cb3b540b08f03026fca06d5a995b7f30e4f9405800ab2f07805df0defbc341d8f3f8d870c776f9284a2a5a2e30fd58e4bcc09614b6cb9d034390165f8cc5dbcd3dc5155e32c54e1419b25bfebb00ea9c6277efbd79a66ef5bc1305ad7e427c6242bdae576647a982db968a0685c7ec75003bd83089afe7a42614684ef4e6429029e578af92ec891e7f1a1a364cd608fc4b3c641ce9a17f464008449161b045f589fcd0eeebbe1db2815579a27e0b7cc76cc5a1e1278c2daa6330a2905f86e5fed68e7dfe6196e3d7a09c186013dec04fe82e4e0ad8096526580259cb4c42f18babd146bff84e0cdb378d66201c5854f2612f8d3d1927382e86f1f3a6b11173ef684f3feaa7cab841a5622f6f7b4a19700f89378b5c2f63207be06f9ff302c665a4f211a5ce59a581b7546766b15be91e84f90c157733bfc3ff982d9702e2e9c507b9dc5853eeb137c732139e6e52e32fa16cbf2ee4c168ce3c469ebcfd3a833d11cb8cefa3736a0749043a0878b8036f07379d6d91b6a8df646a5b5a5ded01bc0e8515a89c9af45f68d058ef421affa0f880ec773a1c8bf3e4ac1dcd26e5d580e8e60c9d735ee8b33bec6a5b65512bb20948d5f90bd2d6b7b2d5f5502d354fbf53c625301da0cd84ffd5d8ba0f65e6c0e8162b00508fd2d241a605f8a22aaa6c319e58f0ec79859a5faebebd5c005c1cc5a32c16ac53e84548912341e2181d8bee784c9b650d81fccdbb762113f030cf2fbea2e2d8bb2d1b5cadd9ec66bbf2741da6caadda524e1cd91f6d2ec0949343562832e9d814cb1650120deab3ae87fc992fb7da95be59426f049b27a55a615d7019c8441865850ee16bfed6fdf61ee1cda376623bf1e85a5b62140ab8dda80f421dc6b103bcfa0b344e3fa9bf469fd13ff5f849f94216e1222a294d538b4173b36a126805689a4819dd2fdf8921ee34db697026fe22b8a328ce9ed3dc98fd34d846b4939d6d52ba521eb2147ca66b5793a23ea083b57334478fbf454ed3faf7eee9ca8dc081f07caad20ef15ef07a30336695aba0fcd6df55461fc6fc3a956937ce5e33492c2b65f07d6e6c1d497a6c37de49e3ada5535075866a46d05df89fcbdcad3824c3dcfa6fa5b8aadcb9f58f18bb1ea06af1b70c9b0cb7742c3eedf704dc1fe9db64c97f434e1921c0f04fef3793cda27839157137fa20311b65538b15ea6d2a038820277b4a87358b5cf3a5bd65486cb58c9f621f0dd3f8868ce51bbd2404df8c9d8b978ee9f069f048ab05903b98bed45713b2c14ff5767170f136a9ec8d5600142f63f67c053fa4d2127771b126a00fecbf3a928398342eb7d8102a2bb0e1e3ec702884b6d6b53ce79270763d75b60cf7ed52381a77139c5a8599eda8838715ef7bd24c4bf7afc7415e401f70c75e8587f41b1d3bbbe7267737a3cf49a6fd9ba9c0f0f7ee367d26b7e4b4de47516d2daf30cdc7329265abb2c64851acb4afeb790b50c120b6c1b9bf207e5fd59643f55fdf4f78aece68a130d185109016a7e96af1d9330ab85bac4beb9a6743b46bf2bb4c08bdfbc3b5f689b52b92f64dac136f21017e0b9c8783a7f3347ceb291a70f6430cb8859a41517cd7bc3feb75f188977458afeb1e716203c11dcd29322e0333996fb3837b29cce3d0b7f9ed69a9ecb88be5c73d7bdfc4cf110aa73e361ae7727f84ee481d73c6acfbd1896f9dfd910bc7f6b180f1f8dc3eac9798b31cdf76a0c8f49ab6f9b181ab6f449fa787d810f63cc9b4587961b1bba7c3a8a1161161d57f3a0f5092921e3102990b892be4648f5489874f5c649da35b3acce2a02c8f1e7e4cb7ab1dd4785eab8178e85462a57cd540e0b168865eac3acd7ffe1caa98bbf4d17701c25836b01fe07787c75bc24b7f0465c47b4f4dc3fc25c730bdfd610fab98114eb86178ef4163ca54ea6b47330dfcd40ab60229abccb3d2764b7e545b476622a3f58730e46a52b16a77b3d2b8fb9e18e139d740a31c0a716fb2c6ee46f68db5a51ed3e4205a86365edfdd98ba0f4e7df533804c3d9d8c320eaacc2e273ff64fefa11ab8d77217fda1d0bb3d16cece662bfff6c4b46d6a01ec988a4bec5acb7c53ad36f3a6333c613757261c0c3ee4537b7813e6a2a8696240e5fa63c42cbcddec5d073174dfc5e20a2144798458657d45d6dc631af8c9d56164024dded42ffe866c069e94a1413b6ab5d6c4a9e96051bc35b07520781fd897a0b4516ea1ec7f9318718b2de81415b83e3875134f794cf4f3a9af7d989898a9e1a367d6a1ff0a2c0a282b78ae9fdba290b0c1a4b71451fb6a6386cd85e00528a305f8eea146cfb48a4886fab274ae93881b6904d357628185b5031abc3de6cbf8be62f63e2df5aeb65727f29017b38378a65b773d8082aa6a7c11486b78add1692f5c38606029e0923b4b5b980b6488830f71823ca6618e6aca7c6a93296db5ac621bf92f8de2bed7a280342b79b6836b4fe7d3af8316f76177cc1a85e10267e7787e19afb3e8a7191e73db46835493c5c13d0c8d3b916b3b31edaab12db1d26795e495ea373b2ab68f7b5e2075e9c957f7e72c12109563079d56ad26cd7802b66ac77ed0cf9fa611ec83470b2415e427f979fc6a170d1a684f5cac74fcd106fa3a1542931244f6ea0bf0291a82d3e0fc82cd65965b21eceef3d56b8bb12075fe6321c7693e3080d97ffe3a58243cc141e97fca394604054001082d92c0eb99c97f21907978f2673b393291e17d8fe5e66f501f7ef95ea6bc070eae4d9616fd50783ab3430136895f84ac5dbb61de1ee7e2aafe19f3320664e5a07822655e51551d8b65bed1f15ace33d7aac3fca95ef00e41e01a73f0b2fcb067cdc65cca104be77aea571b06ec64e5186319aeb30eb14fe354a4cf303299798910c2693fb630628b64195de8908a41485d5ea771e6d755cb837c1e6e4b3863c1653aa27839741ee444e408cdb0b7f3b3bbcaef736f25e1818bac9a4e33971fcc6f214f627935dbb0542fa6b8fee83bad21ad33e8360ac2ab7618da3baf3d1483e4055d997b1b47b965d142b38011333a8bd2380ca46fdd94ef57571ccae5022278e0ecb74ff64ac4883d3f39bcb10239661f07dbc131e88c09a3a673af0bc9d7cf2cbf73498272c3f2a5a6aa7a42e177a9fef2ece7ecb3a03d68575d195f1247a30a0cf5d30a09c6c1b7aa70a7cbb8982e0f003cdc1ff49d39d49bab409ac0bc6138601dbd9456abf233df4fbebcc0f8a091f115fc15b62e0337dc27d2849ee665b1000b3cfc1096a9ba3b4a81112f57692724967057bf236566ab8f4e5158485ee23e3144fb94ad8489b3eae139501dbe283e7162d36ebd1e87290e427b9071293192798a0a1e1abe55d0d03f9a7aee93ba4fc52860de92b99e8515b0960e3b672533eddf9a1b31b73202a6ca89e03aaab877901c642b263967c321494f1609fb4d53789a20aa1936a2b1ba371f09a87af69c32e655cd61536f5de9408f62aeebe76ffda1423dfd473a86a3e12cd7d22ef6b6a0ff75cee09fece2a6d3826e30c8e0c8aab298e70acde2a9fdff072afa02fc776e82c0c16603af782f1b38954869ef048c1261eb1fcff03f2963583279f39bced18173ef0e1500ec432df530f0feb7f6bbf3186b1157a887688d5cb2b0f1388a0eb33024ecaad150b399ee369b6951bbd7193c78b721e709160590ce75cae9ba78e653ab7898b70b6370b138958dad648b41936dcb90d18269f72f4af0662c45ebbb89d2af15408343daee0ef6f3663d4d24f64639df34d2c39c9340ee26dc64d265aba463fc1a306a2d4ec0986ff1a44177085638260a5b7ebf2b8386e89b5cf44b6898735ab104b25e4e0caff08c8293dca762b2234e543f655cd9a8c51c27ff507a7dad84cc6724ebb8b144c5544852e634728768b4ecbf24712ceac30e7f5723a0c2b1a7d3e10e67b9d37e33b78f79afb66a29226142f9bf3bf3b00b6c38a9fedfeeb8700b3b9217db2892a1be3295c282708cfbf77d76fba752ae148efebffdd0bd4c46625717aaf76a4923f711fd31541aeff99a9876ab88ce3cdd84709d8f1feb97015e4396071681f470b4e66c84425577f9c3dc2247c729c227c2970d600d0c2be448a055c790ec6ec524f1132d0ae5037b931d5958a6a3552379c8bfaaf61e2546dc585c15cd70a9821cea05c16a4e883ce883145b0a01d52d4001873e31397d3cae8a9e3e235a7d6f6ed3b4302f8510b99ca26039631c3280d87502b672c44f801a01de6bb44575c1011156be06b417fc543a77f345519d0176cef9a8df843e0a13740d9ab035b42e13df76f255a4bc2e22487ce900d783a5e7773e5a7d92ca3abda6cd77869fbde2760a6e998dd8f533d5e261302f29d915effa8db850f3b26dc3d10d74b4b2194ee262e4cbaa2861d537bcb7d96a6530ca9807644fa5d6c043a545ea2c660540ee04fa4a6d8af3e7c6cc4fe0cb96709d49c7fae61c4072fcdf9f88841590d612e92d4e1d512f071ef3d96cd97613e39266b56d4b7028d9be921476dc37f7d93b484a10d7be2972aa5bf04a01904a23b299cc0ea580e6905a5cac951f275384939ece481b7789ba0aad5535e6c918dbf6b8c349302d01507c9dc560d244da073851ace6ca222b1d7164efbd1d38ac9e8568afbbe564e066acdc57bbd33038ba06a6932bf4067fa05a1d868d2aaffd33d3eff2b7eda6aad41ceede23a30d3a60ce3817b11ad157d207ff2b37d0c5e1cfca35b006b2f68a30f5756a22b608d91c0d7f2d82e2175c9908dfe273e738153dac93ce17dda0c6addbb033487364bf5de23c6f7241ac2ea4d0c05cbc2b6cea7550b7e64815e4da1261774290607507754ef32a8485b484ef6df13e8985d199818e6196a27fb56910eb99a4df59bc5ed2c611ed24373fc5e3dac2d4f031cc4c46093a2a43dfa1a58d87c2ffc8f17ffdec0801a88490bb1b5cb385202a7a3af65dfbe3bb49af94f9570bfa2b985c5ab967c5f5e39b7849b85e400dcad61ee080a4b8b4afe5f2e13e591510e623422aef6860a7a5eb7c9532fc08a0cd9ddf1179a6deec007f272596e897dc7c13f21b67d3b13fa2f83a185dc2f756fa6dc6d1c7f646d94d11c3139d6a75bf66f75812815fc27e477d5ad1eb6d921703dace6c314299656e25d64914e3cd8c815ce47fed4a40690832b4ac733b51df0b68803b381aa97427a9b00e3c11ae4cf9a18af1506d48d548dd5c2e194340ed8dc9d4d1d22761a506f75afa843ea23bd8e686b72b778de0ec251ff58735dd6788b2ef2d9f9b3e8875c3eafc40531a3abe638778dfe160dc82f1c52230e44224fa27fa85905e593e8d81ab33deb74302fe87914a74aeb7ef1a1b7f8be0e5366b6d3a52f24cde0e5bbe1c9497324d4327570880989e0f619ad1eba35783145173e0dc4a5b3728e45106718c4c840ced83ccf483d2f2cde80584883f5248e6b4bda349f07222d3366b865fbc9be6753daf2bfcb9b437e2fe163af8dfa5cee4a41740694abb5051f3b6cf6dfee22eda2a61b5998f028e3b3da10711852e71c0d5f170543e9d33becfe3f84a1a34f7f0b0981d23892100a0328f4eb5f139fc3f07f8337ab7a3b54a739e37fd1b5037a5a56b6c07d87178a06595f6c60c88e6f3ddc753c6ce0cfa04818d9b5593d07427ab5f2937cb6323b2cfc43640a7a829e71ef31b80c44fbffb1bd819565c7ef2951bcd1647e0b6e6cb23865ed15aa90f2b4adfc7ecf503ba9cdf6f8096bcfd9eea7dd8f0a892c65455bc10a653553eac6b34fb523182d0f7314e50ccc29b60f40679daf2be5a2c62b6c4dc27edd074fc87be9d9142c8fa49487f035c07ae9d39291b6107f2add7f4ebaf6027194347079c42ecc7b8c5e424c086a828ee3a1bd5b7d6dddb6332eac5b66d63e78b294203ee2e5e3f8f9200adf004866abb01e2774f8e9132b559686043e356375e0e1af6cbc77a86fcdfe990e58a2e07eb91003d2ec39051c7f3cd4a24037bf1895cbf589edbc95665e7c5094826947bd8cd9beb9aa4055d545e5e591783ec42b98d389d2dce9aaf3293c139b72133d9757d407dbb8556252bd16d7fe04588cc63d281a8423168e132fc8140c741c9f5142b213bdb4a4d538fdbc238e743bc5628dfd401af072c27dd299f83e7677a5ca108f8b93ca3e0bd5cf86356948397924f869c0e113a44234fbde3d41e297b94534eac7f32481a5e719d3fba2a75f95296d77fb06e27256b68ce9c2511c157a867dbfb23756ce76daefebbdfa621ef593912fb03c72cff54c91f3d938e0655fa9de84e647d5ec1ab09f3db240b90b1903a96a29f0f1b247f1497a42503e194cfd34bc1d848ba62dcf4a1874d6de725dfa076d19a7030421752e36c31e0af9bbebb69a9b068de3c67791aa2ee86de50c06745c5717c6d4e37712bb0a5fdc63731e542b4f04dec783764dac145d4b2df229f35111cd58480201e0bd1100c796ed51fb9bc04dd47736a8c569c658ba0792930ac7e183db0a11aebe2585fed06c5d3a411de1d1643bba13f6c307c8ba64bd5e74e752cb482e54cb832e75e1393f5d2a287ddce7638ef59aa0cb9ece5810ad5b0d02165f07e75d35a7bbcf43fc051d4bf2a85bb021fea0492f83a5fc896c96a17b6dc371f806c1c491048bc694d9dd0f8f18c430efaa46b3f54b765e5e209f7bfe059c8b44f40221bcb711dca8e4128fc0e3e6ae34e8fd31ac3fd677973f18a1b13cf0e3eb3af827082cf6ffc02ecaed29f6b6c102a50f8deed04ddd11bda04bc1107595b7ce0aafc62d8bba7ec336bd0e5effd715089bc64f1770e9204adc4036f0f49ea3c327278ff941d7d578ed6ff1fdac83a63aa6eb3ed9a7c0bf6ee7985bed10eac24982157a1e251c284368be63ed129cfebb7ba2957ca660ea3857c4b9056f2a485c918fe2d43b443ddb64354aa70b32d634ac5cf01d15beaa11b2e25973a728e3789900d364d9a9d637948c0fd347bdf26a8699bf8002bd8a5e5c04e116d2c9d2c9da9ab9794f87c4756e414c127ad0086ce91f5d60e3d5ffdcfcd09fbc2adc0deba05da77874788a73d889cb34bdeddc39647476aaf52a609dd6f0340ad12f03d323a7745d5480c9859b8888495be7dee55d9060b1b67d9a39358fa2f416b0ff4835910affbab8b33fc9830e1c847f058984c80b55f2c8323fa29b11a126e94aa9303e69b0a1ef3ac8b9dd3b21bb2199b3ca31be7da692fe33323051328d32b9cc51d328d363902883daa50bd5ea05e90ff2d01f9d4b723cc0ad7f88d77d87e8adcf9e065f8014ce27ca5a0489adafbc836ff57ed3ac5226d8d465febf541cdd3611d75857f6a11b137b428b954ae40a01b1fd901d6335557a8a079a73e3470ffcc352ad927eeb627d608ece63705db14b82a2fb19518a3705f0280c8ddc1cc3626c96a34446e4d866637cfdfb8a2435c254a77162aab8325dd327da8bc79721d484c9a4d034f6b7edc3b3c6822da3ebc56ca900179128e8c298bb239549813c169ceec1f81cbb80af26a5a3bc896d1959ead0c806a6514764f5933c94293df7aadd3eb0e7057051901bc5eea051cd598157eb199704df58e187e561d85b4153815bb7cedb938d6ddd42305ad359237a278eefe9cb333e2fd2c8b71bca6c8b06643a08dbd72c9bd7be884ced83556e9f21a566f8ae7d5c66a3ee8d2a0bfd33419fe49cc69bb3ed6bc19bb92352392661454e6eee004770b966c8f8c88a0d149577b0647bc0bf6a6ab6f4e085c56df1b81d786526fb6a566f44e93f7e056f670dc3b435a9da3a224c4b5826be90d09f0a7822318ba0b63a716b16b3858c7b4ecbfd9696aa284911dbb86e36301438222e622e96263d9597889f30d0a995745d7d19701e4efd4eed5989285161eda1bb998a8a286ff5deb7563bbf7e5788686b587698032a031003dd82bed55783fadb72c8d5aaa18ac13120eedcc855a8dcf8f2d696c669681618d3da8dcf17fb5799d63de0e03e9cca576d5a434e8ca7b7eef9ce40a9a0678c6b154bb1de816071ab945b5f7f1d1c6d023032b13dc7cda9c190368154f3c51e7b732d915c78065605aa4ee70988bfe1438c5786c43b8bcf278961f64f62b54b78c560f405d5bcd8a6bac81ca8eae66549d50c9f4f2b8fbf200137ff88db35ca09ca81ddd646224ed157eaf7d93d178399dea68aa0e9527b4c59e838e981d84e98adbcb9fea8928efc97b0acfda2c680bbe64228723c0af4dee1a411211ed9282509e8a7200f9272293071762a740c02a88bc0c7df1adbd6608bbd2317a256dae7cba4b147d5c3629c1b856f8cc46aa784103b1eee30e15021b1f1052316e655cb95010e500775056c835127363cd26f89309c55073abbae6df0d5f43bbe783b4b7f3621953f820a5197874d11f03bae41cce96b6906c98b1e46fe0191221d0ee37206ad96a4345440f3b37ba6af8d0e65f09c52b7f7acbba7b425fee8550c670d26a123a7d41760e2583ec1f8cf5bf4d3f074a7d35f0c5a493261df0aec202a53a7e40afb2f6d21f419b1af8b0361d9459838b22b76178107ddef99356571b2f133a25de43d80bd184d14d655202fdd54fb754d3be60fc45fb0d75dc305d90843d1c2ae7d089bfec4dbae8dd8056b3f083ea574c44a733891d25f66a5e722a3dc0f64192d5c3537c395955c0719b3bc3e8a64d714a01d1a157a425024e10d42215933b97d4d9163332c4141168d91aeea57c22c1ffc5d7a1a686cf4b31e204a79f4a8b2a85d3bfc71e00e360741e21382337dc2a08f83b2401b79ea1f3dff33b2d1b8c1967c58c746c46d9b38c2a5f4b83898e7418ec6e2fb4df4042c369ffd3c7f10c55e2754200f3afe56af35f6b41c9be4ad43520f46e62e247147048ff1ff29f8c180cf5e2331bdcf6f0df3414f9a73270b57bdacd8362ee8cca9c467e7c2f47d29376fa5fbdec0420b8561e93f4afa4b02d34bd7af2d69a4e975f9f4f64048af52672f0df5f355e7655a7d2bb552b22af3c706de0070240c519b05f9049ce01419764468d12aa222409a4143e21b2ddd6b98c3a9bb8ae32ccdc9388a7ab82d91ecb02c24a2f6114d6bd7314e1c289c28607ee38e941c9758b05707244f089691a7fbf9745e882464fb2550ef4c40a08e5ec4bee387de002443db1b10ac20ba856e6726d5c149bacdc95252da7716925f355e2e0d51f89a4fd290daec3466db3a0423ad65e2db55b68bf91b65e0d6c9cef8c9bc2b7a4ffa5753120649af9b08b4575044156d935a935e66239a0a81a4d3bf9932bc1e2478392eecc5280becf6088b41170865656e04af884d777921ae88d3eed51f6fa916105ff774fc831fad724a9b214fbd60e5064dcd0b19f199cc02d54b7b2157dd6fd544d052fbfb94a740f93d4768aee5e5fc7a9f4526407f8a2f26e6f8b5531c90e1c577457d41ed422030a93103e398ca51b861f62633f25c18addfe7cfc95565eceeb9a7e4da4a91d35744afa02efc6b6042dba56c5ab25729835a3f4f59627d9c20ffab93ca87d9dd170fb15de0a6f659826710ba332049d07f7f4dfaeca228c9aede65f6fc66550eec54cbd08d3afdcae688a31813e0d4405de93aca680376f9e05650663c04b3ce198f699fc498369230547ce8c8160d25745a1e8e7d0211ac85f37d6fc05bd75a5d6469cc97f1001d45f8aca0bed35584f171eb5436811837491eace9a94048f6a6cc4ce53b826278e74b8d6252c102195dbff48826b11c131f012c787b3abd7fe29f9b1281746c59a6fea92d703451750e21a8b0e9051baf1e357828c84d2b19048f2f8c44d3074193040999b2223a609610917e15c698cbd5aeb94b9fe1201f4fa99e53c4262d16045eb26eaccffd83dcdd972720a50421711e914885afbef5f230843c60ea4369bdfd9b2363d9e9e6b3da9ba894cb3484f0415ee2169b2091a037a99063764795958174c27108c53203d3f12ff415415bfba5bc01aecf725e9d83a5c3a4e496c295c471c91f34fe770909958757252c87109ae9d88469a85dcde839902df217743cd44ec4bc79f18bf3d08394b6668de3af0620d0f58392032ba88f2c83676cbc350fb396821cf007562ccd2b78ce73059d515c31b2eb664b9ca33c2235f2faa75ebb871692f07f559ea9b27a3716e954ad442dea6addae209cac69df1546f8ae41f455c535797da63b2b67bf759543bcaef688a9f9ac6e447ef8eb14e54b0153df42b3fb3221ebf1698f5b27bcbf7fa5565b93e47ed862d6111920510a7dedc4604e0a002045793a0d2baba034af855236f7f4dc72f25f79d9403cdc7d325004276cb1ffeb194f137abc6cfabb5f790cb9cd39e1bac82f5780dfd363b088a52e5dd0a2090ffe7717dbd7c770df078ddd5a0f600e50c6294b88723ce8724109d3ecb5119b576a9ec9b178a848a0244479d13231cb492134e3f86e8b3d6fa1f188daa754fb1585b94d0b30f605b30107c4a186f8071ee7aa6b4eed124b9483196ec747b6896e4ba5c56351cd3358be4a81e260f0f9da80d049d6b25699f0a862c6fddbb838f26e9cf37b216cbc560b045eb3652e46f3258e1eb81991731e40ecf0f941c9d55b3633260c8a485ee355ce476722d95586879ef52e9c463c2657ce925aad19d0ce31c8613245b4f74aa7de1f7e1222c210a76cf5d4a09076e3d5276920482b97c0be2fe1b9b8b9ab903d388f3eac2b0a3bbb534ac5e1cae61eea3c28825595eab9cec7385e00849129b2fdd2bacd90c33ff3267e80516192eb81ee3322af4561e7a175067f466afbdd53d976284dead989accd7e98ff90247b98e939ca05ded1fc62aa8d8ae596ca3a1b1cc02e5cf52a82a1f7eea8bf4cf176aa13a20c0c1da1881ec6285b1358a3323c4dca23daa3b6938b459e0b030b95530fe283a9308f06341e555bc88c2a1b9b99335566f8667c892fde04843d85fe0029c6f265cf04793f7f84b7d318489b529f057290dd5acc1490f21afd7a81ad2ecf447deec5319b98c7def315b41d80c43eb0f563776ff7788d9f77761301ff7b66583243787521e8492427787b945d6ce9a87c3947eec479ddad4375c1a6e1eb5874239bea5ffd8f0ca84a02808e25987c0bc249e0ed834cdb2287f188cd244f13b5f9086978df52485438906246ffe9cc1d562ca4fb3c025634bb98ac9c080cfa25c84820da89d62be56e981985cca0b2b1c3b7d256d46aee1c5e5c8e3ac0dbd4379e87e53c1012b7afd9f357f70afbce3568ec29074b6ecfeaac29c64bc8dbe5adef0a3419f960fff34763a9fccc3cd6bfe38c72d5303716dc0cace5669759666aa7d99877ad69abd3c61582eb135bff3f85b1cb986788adc54961b9be390350dfbb872e5efd3b14684aa3c1c12125c25b71edc5661d629c70e2fae630ad577e99c3d75b9682912f024c217e4cb0cfb68a25000d549ae905f0cba770775d90c41e404cf386bb23aaddf3f909bae07b3c2c03dd6d0cd3f7bd2812d4c1f6012fe1ac068064ec7a8736202ec69d90811570ab682f86016bf287b0b81f03418cdebfba31e7780f020dc2fb3a20f6140d7b1db909dca756ef3d2e06fe68f1feb5c2e92c78099cd88bbe997cf32988e89252d310dd1d7b8ceeaf99b5539897a33739a72fdf794d68ef6967ad707e98c63467e18622601064e5ae2ffb8d32227100fd83a4e3e82dd0a8b3374c9d390e2809f48978f7b1aabd2cd08cf10588a72b8015bd4fe763b796c88d05ccb3f67ee35beb2b69b741615432c039b412ddb27b9a99454e97a6ee17a34b449029a500d9fecbc7620a56c9eb7b3dec846e4ad14759583f94bd779b5796d9d038caad828586f0332ddfbd6772ef8da4113b720f58d57dd6971795b675f921cb35d7ba9d6b21ddcbe78c97c3aafc47afdbbbe9cbfab98c8fd952e7ed28f217fd39f47e7c83918349983342d856ef3099a0672b6ceb755785e9eaf7b7bb50365afaebcea33de063fbcb691b5e004fadc77c4bffb25e1b0bd4c1abd7541e8d5080d73631dba7f9c6b957f33bbae0a418034c209ac369bfa18d91e06a592e731edf04b41edf3afc6978d330735b12f799161dadad91a7018e69c1064235201b188c069f4e2154364242a9daae2ccd698c00b1c0d8e00636ade2f06e1a7a44c49b7c01560336de394d404a70ff26777fb0ccdde317ff04e7c08e7942d6e42f6f6d7dee54d4bf66c3d740b9914b4335dadff635259ea42199a0ab2d5cf247b3e67468354487d223afd0a82a447bdd81b8264110d10d0c9d85f367aa0e609afef4f8f2be2e38113da7c4d588bf127797576c91aa2a2945f0b43e22aca343534fc463ef3bd5208ff4b7ca78c410118fc5748ca0096bedcd6f1c014ad5184fe7da8be1a453d4d27d87cda98012292b89a5d0ad5de64a79a927379f5dd93ab92d108c3d3c65f5a5a000c7d3aec34c384de3516200cf687f47cae8ed4019aadecf3bca0a1f7d02e2f1e8742d2e493c4101b179d3f36c817d83b6f6f796f4b4b843201bdee31b9b560388be59253d66328dc14104369521a5cbca9887c402a2aac0aa10dec8fc71e98ed05b6939c32e2803bfa28e16050cad16e6624ec3fce6df2ff8f39c2d1721d5802ce5114838c190b982b1b91d6a28f3483a50ca2c5585e5364075a2880b6ae5883b7880bceda1ddd62f76e04f88f5a05ac429f0af4ffc55c733852e8a5019ea72f2396d4c1190d69259cd2bbc15fc7c14c6bac6e62332478d194fd5f42f292fc0ced589c380934f716d5d8bb62c010fc00a2240157cdfd13ddec58b9bac3e288994d8bb136963deb8f83431771ebc3a75eeaebdddd4c85ad9666ffa106e46610fb8b33d863bdf45a32c4137c60585632c1aa62f475b9ada0a452ea38ab501db0c50bacd61bede42a49deaef39b5eb6b840f0c5ddf060842f738674139433f2673cde7573bed865d8df2e86d5b7a538708db33c74469007ce2826b275b0a22dd74b4b389180a615e3da8b459b0b8f2311ea32218ecc0598dadbc1fb84f545a8d623efa41e49fda843bf0faf73d9a19afe5d0ffacd3b1afbac8dabaedb732d8bcf9f5acbdb1414693d510579020f03c9ce1eb8c0d87480f1124c31e2a14fdcd8936f19afc8aa0c13c1642c3970c1c7010f628bd159322ea8574470da3ed81b8705863723b1022234f94b45a420b918624b444b711e7002b1b0c8bcf3aff82d48dd1b133cb0c0b2cf6174d7cc6a3afe86de318c9ac0d09dc8bb530d8842a6621766afdb3574d7574e761c49d3679910372a5dcbbb5a1fff93fabfce4b6347d00c7c40ff302e5d9942611f3901be7da7b87c4f108c9c7067e669e89eb09fc5cf3b6342e8d4d22303393a5fd3f9a3a0199049234094491d5dcdd6257fa1055f2c78b8bbefdae1189997a29e72ceeb0c2e5ea9d104b5fab7fa50a3155dfc11701195b99701153f240a8da7cd97aae9fdc8eb70244e23bc0807f446cbd011735af11a6a589e6ded52da15230689cb6df3540a86d14e15ef7a9cf1098a97ba8b32ec8e745337b2f88bc0edff06a9fc34e5612d56b9fdcc6ce0d02eb856c003321a6046896c271b760b0258f1bcb49037d5a6311db5851853850483ea17e9e3244df4ba9cc408444982f7acef26e5d695477141837ce16b9f9c530a31bad5419ba0b51908355b333276aa7683796c2d32cc5c7703a5caab5ae1b6933f04e65890e556e10c25e5553d69532f10c7f8f367b5aebe15d2696fb2b684d5dfa6a40651303495d87b059a7b23553741f87fdadd5120508f8dea8c9257c853709d32fbf1a091ee439c583ee9e2174b0d1405df09df91eaecf602acfdb8faba3333164fc4777c90995401b92cd06b1a32045c4d3e3c8ecdc140f96403a3d3e4e68b8eb7e94006f688e6bf4cfefa2eb254f6b806cb7b9c2aaaff343a1405bad14e5d5af0f703788d8256a8e936c145f6a55904a2de6a82cac45221f8c4399ad6da68458783cdb04ec022fd4bfe057304a877eded8204e4dd7ccd0468852caa83c3ec5583fea1cd5ab2d7b839b773090506fc786da49ba15954727ff3d9591d0f9247506c0ca06274da0ea60e2ca4da1d63c98dc08dd3d5251d77d1be2c816bd346b7a3a2270f761b3dc57e27ce001e10ea8e577fa7142ef94f18cedb28ea96927cd242b8010c8511c1d0cedbcc7404fe479aa16ff1101f0159758fd19b5db6f383969bf209a7e2e9caf06eca3733ef518aafe86f4e758cd74be218ae4c4e6069e6d0ec4463f121b870f35722879c52cd0e13f6c3968d854ec2342cf2ac136e9f94e93b4be53fc16b9db7aba8e77f5dae578f8d526aa70eb3a77c1328d1b236d27e50e825ddf03920c8e70f0411c74acc694ae916f576e54ff980c9a75404a6ab956ae3e2a0d77e4912991e8108ca28d8f1a35178d6f4cd984df6dcfd85ceb4b74b84e91a424cf23c85bb01d3df5e770f6fa3538063279a7501f910861f791aabf34c9b7bb81fc45cdffa0d9429ae9f684fed23c0fb2c5fd1bc2ff5fb93f9d463527d1523520fe5a28f7492a2ec9fbae56ac8d1410c4f232035f31473cc3f97ba3539a2a2337515065f5058e8e85e1afa942ddfbbf730de2e7a6150ff23e1fb348589e2dba08bd5c8c1f1248c69fb0def19bcc97eefa082c593d895b8e0285960cc502ff6f962b6b321f12b83b94d2bf361b74f3cb7ddc80e533c1146b732fb03bed94aaa0624cb55a1adf96e0c2a4e8fbc36f0734f9bed2073359d7bab0b50eaed2bf404a8b486cd7958510f1a415bffdcbfd5a7ef79f4c212554f3384344b844de292137090861a28f5797b7fba359288574793bed1496bc44277366410bbc95a5e2be4e9fff81f35a0d69030ed78ff99bf958c78c4fe96698f9715ace80a8759e3e282cbbfa6dafdbc395023dbc5482a3ad077605407856e31ff75f561d58ece0517ccc1a0430327e6e351cdf0c12ca679b07255af470dfc8092d7c308aa03f9b202559a1dcd3179e89f7b906afc04ab067e38420e8ee6ade6dbf27b6e5567cafd8eda6a71d73c9a286415ee5e4c192795c607ce77a512c7fe3e582879ae2e1536a6769cf41bb9bfe1df6dddb02f85f88fa298d7b689a9eaeb5f4511fad07918fd8518a57bd56ad1e630e55b989886d422fe0a427eda43c007726d721306ffb7b0eca5384cb635c3d2a60dbb479e7cd9635ceb4ba902a7c84098cc1fcac26e81b9b02a33621ab59ae4d9b90690340033ce8b9d2e9bc54fe847e09a7001e504eaedf3244cd7b475728e553c14b2281c2034c7615a60c72034c8760ba3534a12c2e130bd29d68a3d970bacb6e66b2b90881b872f66eb0d8cd7c5be1cc34dcf35c90388776f8bd871fc3e0960ee77551724df1e9bc5ca732c62f08532ae5a32e15670393fdc8bcc89ddc731edd86ce43fbac733366b6ef1cf6cae15cc76913ce0fa3c28caadaad9072b1398ecaf1610096d3cb53a7f7c74c87aafb009f930bd17468664e937ef4ea9a3b3049a93b58c38b917d9cb3593d46e63ccc1030777b8b09b961b80324af547f7f03e600aa5bdaaed5729537c6d496bf7624602745e0a12f026040aca9088c509bfa406aa8ad19fed45416b32e9a04b647e7647d4a80fac30a8ecb871b55be1d1b97cf4b5673a232d8981bfd191bd764f68f2ab2869196373704436065cb6a4d7e2d38754322d01265604dda952882bb967d776cb11f24e4feb4102ca2c4816098d255dca97882e3ead07a39a1525a4e4e3cf51ce4644270819cca5242567ce189ddf720db295c04bc817b881cb94b76d907d1300c63a80323a1a9ec0173666f5a57520689acbddeef2b0e241a203698541d846615f2b6eb21f84cf041fd5690501f6d0e130be10cf1436d11d8019ee0b7769489bf49cfe8c1cd1de439a8c3ba6d59bc41af0830593d9a478ed280bf5ea612a5817fdf49adcad378838b375b6090886085ce90625b54a4abcca6a7fd3e5c2fcb98457984ad27d4230ab14684970114f7f57bc963465f965afd5e27f521e6a557553d4505e01f42a1dee2fac4b3be5d43dff4f4e243f9039c67bc1895d497c83cd01eb812702af61a697ddae636aa4dd7833cf846aeb8a11c1cffc84448ee5560ce9f9ebc8f983c1722b7922575f1fae8f04b148a50125b6dd4fc7ad00055053f61464926ab1c6fd0e82cf11386724047c913fe14139290777a27e52f1423e8cc0dd7d04cf0ca1c7bf60963d9bdaa1ce89f65dfebe2a5fd9bb2c6abe3a7cc48d9f053bbac81c0286be133b3755b686b651819a17c769231187577854e0932ed97dae373f2c7a5be3ff91d6afd35ecf86333b8705ebb3a69d939f3d6ccf34f927f50eecbd93a3d56cc99e841465e2c4a8810632d6be1ba6ee272ccc2e1609f94465598463e17e8384d78c1246aa4ba63a8dcbba0fc551087e294642d4fac689f92b86d18c6017b02e1f0fa8aa7e2ed28bd62c63f031fd481a4d7014dda1a29f52ac033d276b18990f42ad74a64d9873af0213f211377eafad6306bfc147391cfb9b9010b9d51629da1bf350ed9f4a51aef925d659b2708dea94d881c2715d3dca4e378cb1be92ce0e745d4871337bee1b7476118f2144110ec226f7f61bf4805ca53cb888de71a16ef07f89ffab1554deea65689d1700c7fffe646d72e43a2c3181ddce33af4a914df6c79aa27c4eb80ef7a6c71f054ade61a1f504e6d1e3b1aa86d75f4bba275239a051a62816da0b7800da1b0911609e2648d56afa282d4fd2532c5ccc7ac60a10f5a8335caa60ea603180b91e89c2570ed8265a98e2ba4b739d292823f5c46e515bf9f2d8ab13492762ccbac5c8fa1d3dd3dbea406c7535dac49ae823d158f9da548c768f7a69f71c66a6a7ba130fd905096f041b4104eddb6002eb01b2f866c7e8d5f416405d9379c732b29faa860ebd76971cba9dbcad633265b74d98588e760bac78912273ec081185eaa216bbc15874a8fc12e14f5837ad6df7f71198b39c29bc026e21c7b4aa87318ec06f28f3071a8475a07cb710f61a0403c1fcb2d2dde3d9b7b1bef9fc8c16f3158b01b51ee5331c3883e7c0205e5cb62bb7d28e2478119b12a7bd06581477fca4dc8facbd401ac96dfaa9fb65f5f08c6466d3787dae183b1e1f60cacc64c8e8c29aef8e2a198acc12f4c27658a6eb968361a24d78fd1b4a00f968fcf0f351bd5981b7ec731ad554ce0746aaf9ad6e1f14af5de1d47142c9b6966634c4bd65ca47f3fa92f10c2e20465df8b5f9c7b18ba2f75a9d2632fc1789a74bff6396c6018af8589bd0ed7f2128135e1231efcde95de48cc2643de88377516f90b7ab89df3585abde1423a76c55c051cefbc758ce65e5a89450732da510d987ff6429eb01995f5c04d3f2e5121c5266ec0b32b1c58b2af03a5145ef23a5b5b65fcaa6e014d7ba76f5ca2aa4a4031bae1bffa485d818c5f657341863679c724de6814bad48d1251f2f0e27249adc8846f2f753230a6de8afb83451d27b6c28c95a3dbc62ec4f6909bc3a5353a63fe4edda4c0944870e91a3d71480151cc8b76b632d87c6a005b3344b9d3b1daa243a1cec7d773e3db809eebc583e2f49d39b798ac3f7e55959313103692ae512711119a6a71eab47265055a669dd0cfc795002a7aba19c5bab105a2cb7f033e104d9a0287ffdcff1558bb36b7470674a446f6ba6a48dde501cbb5ac1de0a3de9948b7d0a9c239714cc274c4eb4974d3b6cd9120b71f1879ff31053008c2dc6c4591f7b6b6889f97908242377d05a789bb0694615c914ca9aa10ff6292ee2c0b4e5b930dd5c5f19f731217317eeb5885c3708bb5445d43025bd508404ac60c46a30cd55ef58235c41826ba0f9f5b999cea18ad484485451911ffbe7f8061191066011818853c8f9ca266b75aa1ac4715b947cb6b56ab49c52a6bb008221bab5071395071762594f342f5a9adc918608e6c41d162eae4863bef727bb666c6a3d06cba5d0412edd5f11f2adf9e17ba2aa54290b6f9cb0e844e45bba9c762176f9cc1901856b8a0b6c70f820f92765796305ac1745853e1f0a16b45f80337038d8615c9bbb70aafc6649987bfd6017d1bed09ca5a563e7e54b6cba08e909af187c70a94fca1385ebd641d6e44a3bf7b661354a1e12d2b562d2cae5d99192091418b226d42e79e6e3d919a4cef161a19a798bf967f330386b622fcbb5c7001cb413ba3676d8890c6ebcdcb5cd834a92c04acfdbd615b9862717c5557b7cd0dca8ca97252ac005645a4b046491565defce0305ea28620bdccf8704aca9dd3bafae24818d039ac465f118e7fe6871848540113d0e69243d178fe1d18fbca4b6773ad361b01efe9088b0b420b4fed027387087831e70645899caa8daed9cfc6555876059127ccfb001fee3717163b9cd1afe4c7143adb2043c70d93fa40a1186416c827d0f16e9d575ca96fc68c8a49b559cb1fd358f5c36ec5e90c27917d36a7e37affdc9a8497a14ec11f0302fd88d0092675530322391a48cd4bea5424ced55e8516620055d04b07aa45f9bdb2235732604f10d2125bd75229e3ae35444206261b3b7bafaeca1e3855680080d894c702d548f05f002ec21da89105c286d867cbac851a6308a6a9237dcfe6cc14267dc5b311657a99f06c1dd982acd2b35590f86e39cba02f7c9065405a69e6a225558762ed1da436b725a6457f18b24535344cc41d398347f1198877a654b47b2028c78d263a4471c1f1f27bf59cef4fe080909620b6be2c37a3ba754ce30f18c18d997d61dfd6214c5a3772893db8cbbc40993daacb21c5c7a7892bdfff3b52a646359d396d22a49801e6b152e24f6ab4b9559ddd1378443c6043c7bc3aa8a1b0cb5b885096b9840443a5024bc97873e2e0fdaacafd35223762f58365d40049f2109a30ae240d45682f2a75aedaf706316d15e191a6ec2f6b9c78c5c5e873566393b84318460aac4f2df1b9885ae96b58fc3e38b2eb2afa03e5fd8546188a72bd9b8f07622df121c72700d2be4ffbe88af5360e42b70031db09457c70e699a6d5ad4a865fea9fbb2c64a5acc9b2cd8937fa74fff2c0668a413b7a9fccd6af056324291589b710e28d0256f9de637ba5aa653839c17218c3de728d0bf33d62b24817e4309966588c591881e253864439926fa5a4806c70f1d31d93b5bfd864da2210bf4ee6f764c59a7443abfb6dc5159c33257c80f1a7876f2c6d087edd6cfb5b482514964c6781ee0d2e119f362b63c8c3bdea1ff8972a96c78afd94b16cbae88e228a49c7de836a1369b5e39097c703fd050fb1fa9b23eee06bbca9c02ad5818baaefce6af2e6c17575154b7db55da8c0e993cb77ddb0730914c8472b7b6deeb782eabb265eee41a68b3a8d4129b50a9c815fa4359ce0563235869d9df68dff831817711e2a576301170459c34a6235bf51d311f43a1bf47dbf7ed0479688af70fb197580dcaf5d08753aad826297ed702c0c712090a7b4758cfa5f9e6c1bbfa5f6d5c087102a49e2c453cbc72592a55b403815426f6660d93ea36208ac3dd67b410a5ce3cf40d34c6183c7eb338b64919c023d47b8c3625f40edfa87cb19e09baaaf42c8173c59b8b6c7b119ddd006732db51a84cc90abfe7288ce3ea4162698c2530a4b7216862d16415cbae34cb8387f10a751707b20e621609a2828780afee848ed86f77c19d40a2c27b8068fea1edf0ce5ef75b07ab5ba035641614e808e6b2fa64a5ed8c7f5bde3cbc69879e0a32d3b5447dcad565c92efd170a0682a76404f747c3632efd0cb0022cd2122c846d684f7073905f92c33657c4c370fec884c6ebc35e9a196f5c4c1da2c947dfc72133fc7238916c65aadc9285129f8cc681a80b9a7d03886471a6bd711a4336af862a2597b0f77a0684e900c65b7f33cccd7678b747cdb8995e49281ba0b9f89a14a6ec43bb86aab28e378514efa422e960929ca4c7d3c97b48197580d9e06b0fb7795819ff79b8eb66f7466247d3ccd18d4978c1e6838da5b2474b193c321f1f8d4d36bdeb084f406fb3c03dca7b09f1f4d2b458fb24457007d0563b860fc74865f348abf5db63397b67ee15a6e4ce77e1b4a70f20fc99b5927f13314a1dd58c7f0f96092c1ebdffe82568c12e762c0ee5f1937dfec2004846fc99174001292ae9cb02f3f8686779bd790962dc7bdb9a10ceb533dcfc98c4280daad191e0d7cdc2b3dedea1b0bce1940613625062a7df9eed2ff859eaba42bf80a527a6d3f84e8c64242f52627bc6362d7cb12a1254c042ba11c78e82d3007db3b148d9c7b8aa9bcb673a1d26ceb5edf6c5b1a28f2c9a5080a4e348fa77ab4d096d55bb511bea3e869da3b9ea0c40448cb4482db74b6f3db83ed17e681d7b4e29390c2b12f30a4bd036474cc8bed3c5032f1fef3de14fc47b5183f9f052aa7b8e10061ac55099dbd09b8f8a4cc1f011be9486be1630b654734f344ab811ae7bf41dc1a83cc3044eec2fb047ed7b7a3401b803caedcc5fddc4e3b2b47035701812cd5e85a1d3059079c85d757b6e5eebe50625c44153e4abaa15026b5618dee46e61c478fd6c7c168870eb1a43d145299c0f22c70f0224c192572a9cb733ba0790f96ae64da1956ac9b1c6c012fdce5e83a15969e7f73515bf749369969e4c0a86cf1662fdf0ea6efea3d04f313ba0876d3543364327b91c57de10704aca003c147e0c04acad8f4cab24e8bae5ec8a80ff465bb8a80b2a8f64eb632dc1ca5e90fc34f1aa8d897367522fce73be42b83042b89779cd9ca9bf635888902d999a0d0b365eb8459f12ea09194375ea0843c9c854d197f4838389b45d32d5dc0483d3c415dcabbc7bb9e8784dc7aa716b916728fc167469c16a0a70f6aa0784ae437e5eb6a0ef501ad830088e3885c6a7d5758191f524794e13d3ed67878645c076239962e9163e5a5c6906eea2c7bbba57c19fe5498af3b5c6d55b016c4e901763fbd03d2f1338e1980f4223c3a35c66dfdfa08dbcf280748843ca0c747ab31f42d27cee7b90b7604afb4785877a8f3aad9563cfba2fcaa647dceeb3a7ed6efa8e3765e550dec3782c3f0cb04a1839f599297b9e5b8117a495129088b0550feb6347b66c504e0c3cbc75a34534a9b360179ece910bd1c84cb447890373f1301e154fae99fea4004c36055565fd271f78af3bac33c86a53bc5c3e3bad7caa5ecfe06ff34312d9ddbc47db5146b5c3df4114ee02abc4bae77eb7f70080c49c8554f1e057aeb8b1b681ef30e628f03b0aa49f9538daa0ae5e94ecb23870a6dc81e7d8b5c5255f4a85ec650127231b1143470481927f7504867b87c909433d9208825b04950220f6e85be20bb75a299c89a55a4d7f9acbccd34b63a8cc544d7d0867e86823f8b0efb7332bf489ebba014a78ca5b4015d09c4974beda1d58b32adc24ee66d3d1686b9192130b201ffcc89dba0811d8214202fdacf347cc8a3def3dee0dc0a43945a1562d2f7579779803b941c4e4536cb1efaabf162411a0ce934512a472c89c9f911a96df8f4ab7be7bad9cbddde180a863fcbfa3d8b96cfe98d8548d75a0e7facd73c199b7bde838e0a1392ea18ed1263427549071904ae3df828e06230796e72db77109cfe87c1e0f1ced4f1f057016fb9e41ac31d76e79f346dac0addf35f32b6b03e429d6762c27cd6180484a5d6b5e3e52f64bf6540bd07e4047dec4a31a7d1ffd0357d1d4783bf2f8b2a399ed388f09d40abc90bfcf1c0db6d79e3427a1290502881e8ae26f4142078c5902d408fe9dc1e9743b9af431f7bb5befffd3db5e1e58bed28d177439a4ec1fd1c36c6b41080367993c3a67cb2c2b6f89f637bb88c252e2274e5ec2ef9c08250724a656fb762a6b272d6d6e5b64b58e6f4f0540b8fb8fbb706d2d94e9e04d4de16d4ee72e99aed955bfc453268b737b5de65b47f99f16784f936d1286f629d7a0d7fdd1a99faa488f23c4445cd52552ad2f19798dcdbb1be04717bf4ae152613e29ce6dd2bdb64ea7d9b929e9057c835a258d176fbfe2434131ace876a35c10f7f2532aa6339f397a625eefb81f260743a409375a1f4b67457936ee52915b4487ec135e48bba4f9aba022d1015cfc26ab9013e0fa68c4746a33b575c77b3b14096f8ec9c6d5c0af62d9dfc56851b6c7acc9255305dc7e7568f2e03c0fe89c16a5643c31fad0eee177319efe14fb7e81686b18c68eee992dfacf25e34fcedcfc7f25c8c60010ad937576e1e47b49ef505ca8851d918714c039ec858e5a85aa2626fd28ac3fa0b9d372f43a00ddba3b663146f7763cd3d14fa3db6842666a72ec7677a05a942f6c40be0d9662f11ff337422c6386c9b885f9d9a0008fa8a8d534a79cde7ea7c773629dfb2481eb0f2ed1dda7654713c4e1e829a86b773ac21964dfd73edb9772031acb83648f6e1a1070f04ef34b7a087219e735876a640d50b34aeec9b32a00a5936668a216f9176446bd1239cd37be33d5a39a6e41058cf506ffced781216c6b9227db3ad62d0c7908884af9ba0ef6babe712b5d21a371ff5baaf4fcaf968a204aea3429cee39e11fda0b1e31a16849026aac0251860f10c3b1154906614d7d9b24c11d09faa9922cae24acd5cd0dc72a338ddad64fa27d3c001adfa268fee79844759df6ad3fb97360c808cefb13f3c622433d958265303f8558acec0aafd22774afc52eb1ca6c53bc60629fd107718abe7de7db08d2bdda473cf81e72a830c938437e40aea377848534727b7cab622167105c1150b8961269d239160bc8f13f37ccf2f82ef9b5a17997074c35be23ee8eb0ea30a896e413723429a30badc2e3c51d44e3f9955799cfda6c5a2dae2ca57a1399828418d6d786933012e47ed79903f794c641f9d6f0ea45af4cd4d2f4fc75eda1d3b59570a3bf11fb6646843ad2617e500c8e16b5176c61d0fb6c9c4e5f53b1ee650097183cc58831979bb8d9a94b610cf0268425f1ca7a05f0c82d1bd1e7e31611006a4458d9ca56b9af2fc2e43588f0e336d55ba011a5f917df9889c33bcaaec4a0142ffe17cbc2f564fae6fb9816a74476e579e54cf706a0cffddb6c0766ad3e823b1829c1fc29176c21c69b505c42e42921bb4188610fba1f9e117907a6e0c13d353851aad2793bcfbf98de024445ea5ecbf48ae0ae89bd05f06b5b5892174454c55959d3deaeff9ca39b092f660e13b2820816800f8409a12cad4259d988a6e2ef857bdbe6ec0b9d5ef4fa87a78098d939cfe919930735326211a78c8225f0862607e73c1d09652e532303cd4380d0400279185123eda1021f2d530d96674b97883704895ba9d358333ba9253613d2614fadd80da73de8f55cbb79e7ac9a5da576ab472fff87fd3e2dc196ecfacaa8d1281c099a8d2e9f7b07bc898ee587f98ebb20c62c094ebee7ff7125534f930e427b03323fce9aacab3c871b6edc5083069c3e283bc0285c079bba0f6b33bc6607744a739cdc7f7f9f4a2b2c831b4e4e2e05d48bd3294cf119d0b8c168901c8dd4ffdcb0c8e965156e8abcc70411aa1cc29e2f9c50d735fc33a85db2008fabac14fc9b5cd10a18c233413240f7f25b01536094cf6c94a72f8183bc75f0b747f3f3bd77cad432585982d3156bb5377c996d6e7bdf01c7644b26cfb9e56f859b7e415812abe26b3b6dede37ede3bd6f06c80638880dacf51abcebd6beb0e43a6391abb2ba630955ab57a46462e0f6ec4590dbd537552da58cdefbbe57ab1c6a259317ea5295e272aacd3aed67250e16bc706463cf61d7825749c7206ef4c50c9710c88f1de31d55b76c90d7e169f4176aebba5c304d7640b702fe18af10757434bfcb41dc87264d318c0996893357c233220562fbbecaabbf7d195d95f398ca9ebc1c653e278cff15b1359da936511b65ceb3b704d659ddac956e15c999b01f16f7102f7e864b882519299e775cf4937408f40e1da720d79bd77b3759f7c9e1d4eeda58122fa8c657886bcfafee8dfdb0801532aaebf6ec4ece06e01d24a767c2112fa52c3b099a5de51bff6e80b448dc8d8d5f257b94397e903c307f8058d61d1a7512e03e4362705da7e438743ce3a1c2d91285fdc6e9d7bac350e5447ac7ac1f2d329f25592b5a9c63923bdd8de61207088f414ff8122961d8cdacd898e7ef22fd3fad55138bad907a81331024fcaaae5f781a7c57aabe7cd75f6dec282c91e5b2d74ca79623c6fb681c57d1497b23f32b912482744df9816fce8dd2eee53badeca673c509613642776bac8e7673e0ee47fde5c19d7b97126fcfbc196666c5c138734e15778c3e2b0a009b0a0a709b48c3f391ccddbcbeab423a8163d9c591ef86006fcb65513a16bb89e35120f27b6e82a1b51b46fb7bc2d032fbca391db5a0383929caee26a49e0c371b0d470135daac7d969b9952a93dd6ca1b1a4528653c7322bb8f1da77525fd3e0c4e656fb7a825f96ad997ef77c315f178b5efc48a2b49bb9c06928840b62858ed2cf03952c3d98b09d22d2e432feccdff423b69dc461a5810e16303cd3278545ff713a7a44211484642810ed2dba7ad17407e3d8fbcd626b7f05a532fba6aeb013af75956ba5e58ca5b02422041d12d2dc0a369d4bb5e26f8eb06bdb99917e3c188b76f60734e833819d7d4647006665f859bfecd788704b9e4d8c24008ea6b8ad816462fb210de2b61f9103ef586fd263e28456bb72568893618b8323c4eaba76afb07fcb5d5fbc4db6d5e13e68b63135b306289290940aec47bdd89af69f7ead9c6748f659a4e7c75862a99250c87eaaf65ad5b848c35acba72e68988672bdda0f07960a93560c9695964bd9eb078cfdc3673eca14fcb59152a76ae0f9b2f9025b28b0b65e896ba81bff12f9cc7b9c49b4fd3b626a6c02f1d48f46c101afeb6ccf1b9f156c454072adb8f55c2735825ae1aca283b0e8e0019b6d09ddbb0771a0e86487259231d7ee6ff6f11381c422c24a1b5f72f94a395234d17db41f0af8c9d3ca9e6c76f4d1454e0242805ef7f9632c0be5a1b9c8714214ee7d465231b32a265b0759efcc0ff3ec7b6fbda9da4dc3b649b2c4cf784a33bf3ee8fcc9f24a8d0b95a4329622af6ae71d662f2f876d14f075c8635d1c6d9a27c8d7159863f6a17d6c45fec7bd5cd918c3fabd442b83c91d63e842d2a40be7863398fd7b15487baf357b09ffa8d543bd24a95ab656e6d25c1d9885f09b0bd34ebabad34e3da2e0de2c676da50f4e18c7eb56c244438c3991b8d43d82922353d1174648970a219529f6600ebbf2deb1b1c29c1add05e0670a6849d7495702a97e6d280b0aab1601645cd637be5f7ebc6a8c281027e0ec9e2180cf93ef0786cae966f303acff6c6ca978501d3ff9b82cd95dc6adee1c0d102dc51a9a50d44db5e007b9384c2f00ef9d2820a06e90d9fa3be7b93362c3706b79d644a4e99d0c6ad8bdf616c80ef857b7044ccc4a8737427b563b5774291dae1bbfc8730b7306fff20a61749b1bf6e73e35856bd6f1a532bb8fb0731d57ad492cbfc4ed30acdb25f860e62a960a607e71cbb4df7d9ac594ccab76d894b53363f96b6cb7ae786312b32df20231bb0eb57cdd7cb413418fe47db2f3e1add41b6671232c47631770fe7117c6a2979b1f9eb20489c4936d8310eaf398730bab6554715c53b32fbd4065e205bdf0bb30b632306f22f310d95c646017e33500558331aacee2a4643829f2f8d30e4ce098a7a814bebe08c2f980ba045c0e2ddc46f33cc45013a832612cca5c366b2c5a346b83c45a02aac800b7f04245624727b436fc48d556a7af7457585173493300219e776fc68be5162a1ee05065126efb1f2144d81fae94a98346bf2b876be4ec6928b6fb3bcd375c28864ebb1791e6090fe7a47ec9a1e38c0e5222dca3e35cf8b9330803907307596434177545ad7a76f2f41976668935c8210b213b79511cca4dc0e8b18c403ee3adea2feb57efa19bb584ed92ab5ef7a54d6d43c01bf90c7f5e96f280eaf57a259986f815fe0815b7a00468898fc7ffbebf8798293550fec2ed1522bd963b3db770e9c1297cf51a05d7c7370f146f0425c75dc44119377b06bb8f72b3b8859a87f518cc03b81467e1e62a99436358f756dd45c9221b889b82cb39277f75b73b9e6180b0e3c556c295db112e9c17271fd9fda9d8c76c265dbbf2c39bebd9c24f25836f6dab5b9225b225af371a2a9ddac4067ee426cbb88b07ec02e1d919b788dd33c46379bb3c8632bdf3f8b32bb9f0b680d26f91073a8a4dfb1e61201bfff58619f7dc9700fffd358b193fbbbfb42cb1b9d9fb38a2639057bfc969f7ec2e04d10812418dd486ca694fb069ac764d3439fb6d9ead276dea0062254022838c68f3d4fa73f262bc42827b23a9216b1e0ac0a24e2d3b914155bc1036aae127f4c7bca69f4319595692a2f5d8e86faea308e2551ea57f34205b305d69bf610a5cc46e6904e0e856635c3025bab2d486ee7472361b17591b9da261d80dab9b88574635fe78b7d30cd210e6043053fc181c94f0a4933b8a5fca520b23f0e218237e65be33a9cb7cbb7dbd74885d6c23b569934dad757a69cff0506a8f54fc702dc062631604cd2d3068c674549132bccb34cc491b69503ade24ad9f2994a31aef5b06dd981a67411906f718f564af1dba688827cf3ddd0c8c91da5e026df37f75c838c2eb18e186bd91dd4ecb38da46cfe1b755fcfe8c0bbd498c256a2c6e78e5825e8983eccc4597dd5744af2345ff0406ab7c87d0663aa42757a01883176f4aabca4c71b7270fa9067c4365604bca7a572de299bc53f255793e429b055eb3a633bbd6616082c89e4fbfcc7828bfa7f97101af334837e7512cdad1641732d951d4950211d8e4de04a847c6ca52df147134732191d0b5b3237ff3d87ba3d703ec0d037b33fdca3a2e29476aa3a903654579f03a77d228e6646a4a027a097381c32bee971c07eb38d54807d252d9340437c4618b4479ad4ed2877dd62d16c79a30ccf5e3fc36e19bccaeffea45b29c491180397dd2bf0ed7b4356d8caa80e5137d37a919fb93b33c41b7e452b718d97c141f35b29039828ac29e6147781ec3cc17ad8e976ef02b8e1b8bdc34173d0c80ca221715fe56457cd3406cbf7f37529d72a9b4e183e76d62f83d3b92bec9dd703e6d970d907fec2bc2b4f27e8fb8ccca0671837be24e373b922c225d281c03b92b4eb857245b8aad2a3cb89fd5aba62e94ecd67bbd85ad589dff4ee43c05f34456692d7ce488531426d0f96e42e0f2165f726af7ded6b39a497dc4d034ca250117d20f2b6ba063d8e63f87de950799221b795b4142b3e6f8e592cc9ecb0a38e1917400cbf91f07ce85d566eb1f5bfb249f93b893d1796c163eb81ea021dd1e6cd93709bb6026d8e48fa50d6d75d214aa3b13fe471739980d492de7c32cda9e03946f012e9be2272532b2dd42e8ba27bee18a19d4bf0ffbf36cc22d41f2963ee9cc8c09f11c60f579205c08e82d27c51f5dbd2be51087c804f19c6039c4c57a0191a05bfb91a017ad4fdd70ec95d9c7d570693757d3bcfb6b4a30cdf5e36623d9f087045813ddcb5063e1744ef30c7bb8bfc235d7ef1be22a1c986e055b60e5ca1ac238929203f260a454fcedfb2fbb3a1fc1a6aac8f89db048065249ea7fcee2fa886cabc0466c58c74404bad578fdabc0e20779131eaef95a24749cd3e02df9693ff6721745d59d5a75d6c068a2116c20e91f2f7335e1ed8bffdf816f6c95b424b05c26c7f2647399ce11609ad2453e6e07842d28bf5efb028540e9aa6154115224b95dfe4db01b7bec69040bf9a27da2d3b41365ffcc6d4b6220ccc0180c16738f54d39febb1297cb0da4603bf4fe191e2453b37b6a8ef7a92a3f4d5a73dd54f3d4167a6932ca6a508b5db27981c892c0150e2512af5883e0daacbe609bae79e88001747f85be51ef33b157c8d091e4bf56b63a136a1605eb10f4263af19fbf3c4b73c74eebe771524640099a009747f58cfdf486708b8a8085653c78e598d176d101b694b041398342b6c849c68dbdd34bb45e077ccc4c5b57f47d43c408e0bfa43eb02990e305e5597abe5e541683b0f3b17cfd6b63763e5440ced81c396a5d0ccc51f8037ad4197c5b7a87e6a12b06de439705ddf6c342d3faa33098368961f5016cecf771d483b10b14684b062c8829bd7128d417c4492c1f7e8e85f3f85b0cc4e1cd634b24f394dcce3ded84b8d729a509fb9f1c5f0aa543f264cb629118a8a68a518f5ab4340b2390affca40270b3cd610d553e37d1857103b1c805a109d52edb977e389e61d9daadd4957ba4291fb1b063c782c9f18dea1b96164d92f5b11b9e57a1d42dc28aec529097e29d3a11cab087ebcef264535a1dfff88f1ade0c745cb99ff608a189850eca38f5ac319f422d6e6aef2054d581876b774e5ca9ba2348a260aa97edcc233d63d71e7e13b62c6a1e517f410f12311d3286a499fd21b9e19c1bf18d1a282f42a7dc4f5e69f4657e9f25c8332749680d8e969d0cb6519fc688fc8ab999b89ad7d7580fa1dccf9213b85d8095b88c5f330858be4e58a45a7a512d4a889b8efff1af8bdecb326cb9c0f11125937e91311ed1c660e54338f91801b56b25e9d5e060a403fb60232fc788b35130489fc3a9566f7ecdb7175a84236f3d7e4e3fbf7f134b143242ed15454ff69af9124b6442f59fb383a0f91ae2ec530984beb88d0c9474aadf53cc8ed1d2658cc1a2a500ab59ca868fc8290cc128123b9861cf6f9c1a05d5de1f763055cbaac62a752997929b0d4db1015e8909ee133df9c7bb730d37967aa1d02a75a060e22056d6b83ae698ea8bed515fc7e105024b5df12ea49ce328471a77a948f334445aa723430b9a92b3b513790668bc2261c7b9cf73fce68931f36625cf0192bbdf2b73959ef54a1f61cab366c661a0e9fe865753f91b4d935c019fbb014ea848d96c7880139437d798c1969720f27d4073a6a849870cc8eea78f4e696e3c7d9e844bce8f3ec04425c153b024248717bf6ec954e5061ad75b780e10eb551cb21b789ff24aa4982910c071c722119d90291144b046356bc0b550ec1c2458e692fd5b793f6a55450f6c5c44a7a1bc94d0454de0444bee795eb1b0ad832d7d4eedc4d758b2c3648cfa9db820e161678b30d2d3c480f4ec9f69c9067cd88a0d65c97e2c161e8fc762331c99deb2e7191be4b9efeb4e614cceabefcf423d39a75cf81d37896184528f254dd2ee339eccca48678801e5ee602c3ca70f826f6392efce15f240c5901ceee0620e7aa55899e2257d3e7a4a50a5366582edbe000ecdb10f9a3187ff5c858a3f479e23c882332f524bc03fee82beadc49ef0eab0554362cd14c5305c6e674cb812ad4556edc5da9343dc8e1f407687dd429a05004713d70afcb7d5bb48aa8bf6cba3b10f46295670067e72e9d9697aed9d753b518e2ac660170dcc9aa69b3f6862215a91d918c4acae7859d98a2f3f5ec8f4d64f3fa8b25305950df5eaa172735e4b49fe01d1b356318b2bf9605e9cecc458773ee7d2f1719d06ba0d486fbc140ba7919d5987cdbf2e2d700003c908abadbf569fbe37706f99daeb31636514aae7340721073432196bad8de95a409f794a7dd49d6b53d025c09a8919755053353b0049a379d055a5a42cd7227324b498f4c05b17b4b9bf7907a98e36f6308461ccb195e1d58b23ec028a0bacf893bab069e77160d3c059ba1f549401e26779c355c94288cb33e5dd6bf17cc0e33db977626e02aab36fb4b22ce254a0d48eb3e419167f72f333340e5db3299ee6103476dba2f548189b14a2e477c491662b128e60ea44cb339b9f488f7820668914353ae08191bccb0ac7afd02eb711b05de959a2d23381a4ebfcd846427aa18696169b172583de321e07ff4a5f2596848025d7b24018b773f7187f6875e37c4c56e755f933e7b4fc2aa51d345d99832afa616ac38c4d6a0e19ea9c9354d6a9695e5c77131ff3afe2e7f8ce302faa62cc2f2ac470f459f02a9bf07aa84d5d13c04cb5452a170b67c3ced24478df7712ee2ad604eb655181180b0d47bd4bda9367e1fe087f96976e511de2fe4d7704dcab5751b400462a03293b35f00723e43cd737a4a130c255ef34b2d2fe9f20afd2f1d8a0c30048ddaafa34d93b7e1c18a144ea075ee8bbfd2d8c9d552b57e198a6481691958e2816eacc32b941fa6a62a88665fc8d2dbe7fbec5694ccda1dbc1971e04b47146c74db736a7a238e76434297cd2124d591529055cdf52cebd1184fc1fdbf1f55818e5c4e83f6e99fe9148f06ee8379366b704dac07db8c6e5d9dbae273c5b8dd747faa434d9e8ba8ee8faccc0a5a2aa0a98e22cb55dbf0117c9adc0eda57ab216ee190e9623331458c853f4654cc8b5d55f062241393bc491992c3500975bcd93c6da666c5561206112b26a467cd90be6a25f830c5e1a09f33a9397b6ace76b3edf7e045358c801435ba8ff5df2be075656a5873bc77c9e862138946a6c1f34f974e7fa0eabf73c904713980e9aa3dbc27b9f46f4774403ff5a4457fb744e81e7040b88b5ca6b23a8c5c65106dedff26032c96022fe813c8674853919ac26507fbdbb9b2d8373e8d2b10441454ab2e2f9bee869bc5ecd615f5e250f454d089a7cb8288d533f4e74fe2cf6e9e95b641d097acc0a5b0bac1917206defa389db9f9d0814d9cfeff0ef814562f04c78131214adfe6c3c2d98ae95a04f404c4456a9ab8ffe1bcfc198d45e106b56de1d8ad81d5d7591131f956d1cd16eabc998dfbc3cad2c4fc357aa2ddeeea8746bbfe674a9660a45efd649567dabe3042f9dd465398a723ed540831177e3f45809d12e84b772bef46a244bf3d612620e56b35b2e2b8713812ee8a65e1dbe9fd8fa94f38abbb2495d4e5e4eeed720f1e16663a652883953e1ebe4f1a0e0b2d0cc79d60dc068d21da87f3fb41d3fbea3cd3f8f259c81ab66acaad18c4170082c316e1f87d8c7080a3e1b936dedaf5d408fafd4f8caefeadff1062161a667ec5682409a48969fb8aafa5e6f5eec5aec6c7e58ae4bdfbc822c0bcefae7aa907f731d2eb75cce5f8012545590a3275e1c351a521700389354fddac6e746c323f0b19611d8052fd9eb8bf67514e0a0fda0b60ba5a1ba871de022c6502b8e90ed391404ac33a9a3f24eeb54b4388865b330397242be77460dd861c25cbe0d1ad9090b5dbf5b764aaa7126938b38a66efdc9f558b203724ca44053d1d1b9cde0af4e22b2aeaeb611142eb6dd324f5c4fc6198cb9d4311eb7d2353152821e93b701fd565a66c0345963a50abcc07836c2c669e3bed4dd9fd976b3508f98054d3e38e90781c1c91601cbbd44f601ede39cb34e713d21d4fbed3f0905f6e47324d1aabb76ca0d2980984bf633bb104dbb7084055ae84ceae6e91b51a17471211160ed94f86658dcd93c95fc6228b9951bebd7e14ab9228ff4d3c223a80a5a2ae0685be4fc3954bf9a1fbc394e1fb4a2f2d8afd877040cb13af968b96b7331560a0ff6fa0552ab171caa54e936bebd6c0376fbea60b91147c651731792d9836903ec5013167f4e183eecfa0ba14427ff37d3adbb96fee6716a94c465be12d13d009373c9d1be399702a79b2624754c696fd6f0b1d70995c1fcffb14a71585e69bccfa248faec99dda8b4854a11678428cd25ac0a773d78767ea23ca19e773801df2ba5b7d51bfb309039aaf2c98164192d93e9e7a12d2eb013b7e3c8b1eeaea437b89f84110eeb513a9a1905cc3920042eaf90913e245d70c0a5b5684ed7e67a71f04e74918436f5738191b4c428c99349bc8be2b0250190872c80d5483e612e955e63890f5ad5b5a34ebf161daa3e7f8e0ae2ca99f58b31795c19eb741373822a9c78ef280f68ba38178e3d6ab09110acd4b4577f52c2a906a11d131f88f7ffcb99a640bbb2ac01d507a102975ff4b673e431fe731edf502be245584a89c39ed3695659230428f4968752938bc7eac5c9142e7b0c7d5b31dcee359f1846e730ca47f0ad71a51bd06eab0b6415b5d19954aee9c58136cb8defde44f621000033e4ac603643da987de9b3490d8d3653756025d1999928b96a1cfb358e5b1c58782c2cf0832503bc4380a7542ddcc7ef26592ebd63d8cd8c14251daba177ac3a368f926b65ecee4f70f86f716d516369ccc283b2ecb3583b3a6736752433317b4b9598b57b082492d2594de66714374da9cf9d591bf3a72beda6cb41aa6a18d6d1d41afbeee67c55c3748d400b3d303bbe85387546b5926b87539bb0cd4fe58aff87a6e0ee536ec3fdcacfaac10883d3072cacf28ee95e5086866684ecd444cd8e19f92efb885a66911fb6d0e6de88c1a2d43e3a2433c911489b445355daa409965d8b3f0b01e0839780bb1440d4f86da8dc55834eded684a38b451f651af8b84c2f7a6ae9031abed8669cbda9c9910c50c33a780ab60bdef4b69713f0ea649d557f7118232af0cbd932c0cd7b7c84dc4843156c0e51769c82c47ae5da3d31ff3b1b1398d9b193d164e0781a2aa7300fa98be413713cae856c163ca7cc2d86c5ae721e1a58909df7a38a975e3adf790d72e6fbf20c812b2314ad8e464abe7c00204e4849aa64d063cb8b51bd0379d261ce5ea6eb1f56a4e49db01e6ba3c666bb3819acb761a52a50eb1b71352ddf2a552913f5b67c68cb14cc44562a837b1b0472ca52d7a278e1afdf78252d246d1f63979bd5e5dfd35bd20d9eb25f33eb118337bb21cdde38832d08f7126886232fda0c1eddd985a29a4bf538d8cedb0a8056d7f468f8c7d109871b6cd5658a09d930be0d7759cff0102fe3691b403e743a9a320f3409f715c75db2cbfa88c4d45ad006c5dd7f1a5a32570581e1e652feb8c005d91200a2ad21e7c4d2ab4b1fe05e08b1c78ffb1c8ed84e2c55009a82542dc97e022fbd0563bf5cf48d93d4aebba7c05f65d3402a769535b31011a66961a8a033632dc8ec81f192c0be82d4e397e8d00e7ce96dc16f5d7776ee5c773f2ee25cef2aca150159a279a96e8bdd26ba638b4acfcf468280ed790920552daadb0d16174f581dbddeeeb0367d43cae672432079590a01123dbdada0079b7d2e7aec65bfa4f6557cba3bc65f07a669f5aa7adc19ed8741638b568c3ce27af9f50ad7f0addb9fac8dd00ecb42cbf295f91662d3c8fa417e3a16f94d774e390ad027822468fd28d86a8e6072906a19f887267b645413439ee69a856472511db29c6be4bfaf6be196096795c35a53d6ed0b5d54c1d60805163314ea0ef956b67ca99ca08a105d5d1731db816014dcb21459d458ae1e1f9c45fa1e6d3a084616be87965269e931c6997fd71b1f369f4a2d034d29a2cabf656e35527b2d9ec9cacfdbef9272944390e4cf7d2e959581f38f88afde1da1043c4ca207b52e4e478ae12e9b8486c184fbee94010f1291183defdc9bdf44295ac421d36f0f9a7dd7f05719a75ce2fa2ccecfa0d8248a8a247ba0f8910dc5282f00d52cc058dd6a6116a36447f2e2873c1607e330867b2f056877bf4c80a914c771182e6c79c76ada2068739d2f96dd33f79eea51d24d252f888837ee116c84b45acaef7f4bb7b1ad09ba876b438854c050aa40d3d3e42712e70c7bca0bb5b129140f5f4bf412e7231225cf5d18181bc3a3ed29f19930289e1d4fbddd144ad2491fde6dd2e41dd8d44e2a5ec24b493273c08987ad043dcb24ec6584b952485a06ad83e464b08965c8b5d040e2dfc7d21aec4346f3283d9819fe1c1daef2499a9917bfc6c80bab4c56dab151e4ab73f18b238842d86ad18424e9a88a80ededfdb2eeabfa35b34438629dd25b4d543bffbb56ca5827c329f10e691a50afcee1462ceca41f271f6b2bc535fd558c2648df1bdcb09035dcc27952b283699a264bbe74dfa004641bdb81be30380bcb0da389531f646ee17765570f3606083a1f31d07ed356c37bc7411c16fcae22b10bd58aafe405f028a6f04a5ed179a150b52ce7ba1396b696e948998d5a4029d5afa18792212a8fb6b1e47173e0dfe746f2c948601c0fccdc1d65517e9c04f487717d3a1b28b9a3c5a74caa9a22de426fae89a3053ea0e8730e06e0299c77864d2163d2385d2c093fa4c735f309995cd2f758965bd083a52cab99ced6bf6e843c32fc7ace973537b75ac53def2aba4787f1d2760dc6b667b70b4504d8ac3633e81a33fcad8bbf50493ab70a9164023998381508ed3d730c1d0ee4742a610edae49d8411263e05c49927ed7c4b17b7b125bcebb4c72fa53b818ddd027035bb14135d14f1f899f96f3e3dc70d34f738458f48e24e703313423d0e8807e785b392b96ede46dae4b5b11b73a03c6ffc3af7013061055f6d4d93abca5f1e1705dd79d0f107223b63b4979aefba7536191da8581842c317f81a80ea5150c9dd37085146e4db1ac1b0dc640143d9b8dc3c41f00944b0da073e9cc720ffc4251cdc1daa7dc63f315f7d89905daa6519f61dbda82715434db8ac3c98dd1122221fba84394b65ca002eaabe4f24f233d8deb655706c2d22076b1b6c0f064f5fb20d0c259bcbbf5bef93bcfaebc3d4f5dca0e0761ce6c7def781d3839fe5d4a18483a2654cd04f42335b91012fa8771cb03a5cfb143b1547248f2ec5d34aac2890a58a6e9a7113efdc55f7e6133c295ebf3987a44d459cee2944587bee95067294660c625853dcc82e240199be71f437ae3ae6b9c159f15f7257f8b70eb4588c785ec60465c048e380b7c4f49666350ad017d90a6a4ec48ba88244a89b5a19f1a30710fc44178a300f6b22f7d8560e847fd1d1bf451120c0c4330b3d87dbd8c94912d761d482a2103398c703ed27b6051e53d78713cc7324f3ba08bf434a367f141b87c84a56139d0fb984c3a9fb9ed733352889a448eb351d1ae5a4308279bf20fecd72b1d04d9402faf14c87a941e79797699ad5b05a31ab130a90371a75dbba4d0bc5ac8c2928f0459ae7ff7371592f1789f1bf118d8c94e23c7db561c5376b5d4af2d3f406aad86768e9562471fae0f4f90f38a07c1ade949f780fd29af6c345bd4ace5d3b8d27782c4a177f99c78f88a0450e6bcf2270c416b2057ad68c85d481e607b1b4d67003a77bbde314792aa0b5fa075742a8ad3ce619cee8aae2f92c97b94e0079d1768a92dd12b73d77fce9fee6856e63a7036d45bec9b01e0c23b0db5a2906a810046dbf6eaaa2f7220bc331a75057cfb717d4538463dd988c950affd4949bf53206c9736d871940f922487fad276be763175f521acee62111c78e095849b91662c34c771aa86e6dc7c1217b13ca0857a55b5d37de647af758bec3ab95655cd0dd8542318a6e376a416693d4cd5b6c8394cf61ede9344ce221c3c4c4e1542dfb123086837d08c195b4871a613595c5bd2cf58951af9805527d1f160dfded0be64fa5e9e0b04712a2b04a0c859fab94a31dca839e9261194839c7e500b7d1c24395ca35203d56f5fa1c7abb2b31e6b21c3c8db2e9704a89ff2b689e5ee50de038d9db59ce281e54428b99f591068ff2fa5ac731c3d6defd6699af159ef3f834840eafbb4dbee4d56d61a61b0221a2f3d1b73708f722bbe265494a75ee463078fd541df5807ceb95c53c0ad2ee1b270ddb910a30e94fdd712de84ba97caa83867ff19aa4b4196bf8f392868a131b482664ea2728643a2f1aec6838c07eff4514184d5bb4b9f93c703e89c603c08b244a1aa8b052ae0be5f7be0ac493a63eef8e6bc86be84abba077e5add3739ab4e9ae33a43a61ab126ee8c4b46fec8dda7452ab6abffaaa1d58b330550b8c6c7e5ad21bd2ab2ab93c64f5a00a4161c789b51659646b4c41c2b44562fbf6e16f10bde3e4d48f51d8dfc84d4a7157e7100aaf37cda2d11d5bbdbf5edcb1420c74c20ae319967ed4a13faa9237f6a7af0ff6e62a13ddb237d514e2ba77d34e0eff83c7fd9c9022b34ebf82f5f4191d3b0a643a3c90f2b985baa450722689c0fe312d848b8feddcddb7366beea4906d1bcc6f608ab68be911f1452010e899aae1cc1cd3430c907bec9abd2cd837b813eaff7826c1b22b997e5395451915b866cb733f3f0eab212a9147712a0b0420f5188711d9336667a0a2e34c50232f3b0cd934112f0806d44cb7b270d3a01554016ef3e29fc509780775462ce096d90d0008aa70d72fa80b23baf62b85b7a865ec5aefd315b25eef56274bb346215f049e4fdc13f8f22a3db3ae9b15cd18dd51bdce537b58f3a3737447b6f12cfd47de69d6da2063c8e50f0a8a4c2153394118ea21bce5a015d420b742be3e1a6bc29784f60e5405f68339310a8ef4695d1f9330eaa1e50cf65960788f0b289bb78b66e516458d3a429e1d8a279fd386c7cba7bb7ff73d539ad45f778ad87910922bc86af17f9d17be2e30a94ee09b553defdb2f908cd96fac99dec2925e5bd1f56e38d419acc17bb782108426a5234daa93dd2cc3f78138cc58894c26fed54e2826b23587765febc4ce7a3da0a924035f86f93f8eb69de0fcda3927d081f8834dc6e3baca62eccc46f1cd9e4a520ff77efe5900042c2f0b5ec9e548bc4cb6d4b1ffa1954ec984340d5614e781437df99c311e365da734a44c86de37c28fb10f720c99fb7f1d94a849df402f39d65141d970b5dd11e38fa18a25747857d17b43b112d8d50d54244303ac27492003039ab0c056736b2d43bd7c3e591cef4109afd8f74846d706bb42f2bb17680eee31deceebc59edae324159512c8f6d30b2f83658ac68a9105912dcc53f065dea79c0473c6e4431af6be54f8a9f02934dacc32fe829ff3dc1f94523c58fbf707b1cf1e93792a26a51e96ecd8954b682eecf15227bc8ddf5013897716111ce1e24f3f1c41e2ac27221eb4a5d10ca2a340114a9dc17171aea98b3f68766e34e297c5de84a1fe99052e7c4bf6a17abae541f973a749ff78455d9ac0b2f27b61ac1b348830d479f369bf0ed1eeadc27e445dcbcefb471a762afda9a9ac3a2adf078e9d2a86efd28be89abd541bb7e3c4899454b12f18c5b7064fac4513b4f19d03bfba737a7a615ace1350dba82c70bf2a6fb2d8f98b98487e337ce49d29f3b672a5c50ce69d2cd1de86ab893426195facdcc9d7351f33abd84c75395caeb1bfeda5c0cf8feae042d06a0000e660dd2bd12d285590e8fd457128907788873bfd7e804eef3ceb9c6b6caa1500b7b2ba03887e75fa5806a6385563e7835e336ea97e2df2c47cd3108e9e9a3a720633346ba8e54f81c162cc482e9706c3eac35b1cc285d656346c48fc8db8a5eb9623027ef884da5273df8209d2680bba60d3ecd3625b50646f7f83217b954ffa7fadba36477c6b745e49680ed7a9e239535770f9c46e39c20e8794f89041ff42f3dfb4f55ac91f5a5b09884925affa0d6bc0155b2785745c6f0d04cdcc6ef7f3fa83f31c8337c2afd25031173e2688d914376e653a18e37636ecfbb8f3cfb6d40a411df6488c550ad5e4e8b07c1b8a506fb77c9e010db78f3571cfa40a069c36fd2237baef264e290bff3255154a7789107a30562f8b5481d8fb6ecaac472e96ac8d5ca0b9eecd64a54df4b2f4848a267ee1cece016fc8cec3d8aaee1e665db4c7475aa336d576bee10374a5e71df4b9760aeff69dd1f372b176e8fb9ae227debef3f9c2911620d5d07e3088e55e4a533743827b94340c4ad5f0c365b1b658b672cafd21c7a0586e70a117594900deb0c85ce1864b0d3fa42e4d77390e426bda287d256830e6a98bda8611d54046990a4172f78499103f28eedb40ae54696f0b6e202517fb9d13eaa385c2b9a0100becc9d7570ff7af88a51986d5fe3411ae76a28442b49f03f90a42ad1cef74f2df7d3252028cee9131b02ee2f2c4bbe0ec360c3b2a2bdb0228c64f0469aa6f902f1f4a0cb29999fb16c17eaf6b7f734c133b4a82cc268a934b632f3b1bbd34550cb889001bfa320677d4a439a37fc704f089e1feebf636b5201da7267739f279cb14f706a23dbca8b6f32a7173f8c802b03b850521564274b09325d88cd2b4218c1ebd29c4d7efac78a1a4182f82fabb6c688cffff97b2907e6f6bf94c42780214d022bf0234f963e58639fba50807efde1ec3ed7a68b9210f3b4a7de6554637b656b47dded03ab274805d7ec76f90dbaf6489fe1d43be5dfec2fc3968774551cd49e38867a30f7562348b8208f5cd54533f6492649ddb7e097e8d7f3925e69f7574032c2ba96dbb2f14ad2d982192073b33556aac6f0da9f6526db271ef69177cb5af6b675a3e8e87669ff2e77862181f4f3b848748288cbe7180e92b60a339e675d3184bb6056d922dcb5bf340b011288c51495a00bc954f0fc5eba581ee39e31b48fbff6840af6bb8eafa3bf0102bff496d034d9f65faf7455719ca3ada7f845bb0bda0a759d77c5468ab0d20d0c0a338c367c6d895c5d1de3172cb83b7c641122a2fbcad51ca97b3797d3a88f38d1347a8291e3dc538552090f221ea47aff1945c936b4437c8e9131ee9e50492429ddac36c1eec4e120362975d7cf39009baeb6a340e66315b36bec883ae493586da93774d14e90acf1cd52b2c9d7c2dbad42dbf505f2a71868d6be76f0fad43e53bd1d383cfe0996559aa35a1545c8b8709931ea7e0feddd51b9d1f65e19abc758987750e2f2f92ade4fd34ac887a7f5ed8553b738c8165c6604bc4fe10d5524e796e08ac9d327ea75af0cecfceaa140ec202f63bf5b6ee5b505fd656a765072cfcffd2a97f4792c1504116cb484739f5f454a8f0099e03a0133d9a6922ed7ccfe4ed7f07ac9ba3cba20cc99099868f4e7b0fddf8652b4d2870d689f8c56a26b8de8224342787037edaadf037e9c1ca4ba5f046fd822abf51b604f2bc932168b5d3a9ff3c4c2d75c4463289cce2c44c288065c64aac49fdc7565526f849dc66665a718975d5ce799f9c49f7fb79a9ef49837a8bc0c3a9fb93b685f01278dc0a4d3cb9b971a88ed4cbed61f1bc2f2c19f7907dc59c46b445f8d73590b0971a002b58a46d38a0e79e12ed183294193fd488cbdf79d04d51b05721b93615d4d0208a738627b8114bf9e9d4392c31fa37bd57f02ab5991a94dbd02e5950f750e99439734cd07fed24748fbc1a3c4bb4b595f25a3fce828c7c0cd55ccb7a60dab63d94ea9581c497bb1a1aac4fa54254ce1a44edbb903f6594cd71a7558ba5afd4010227f8ee7583b70ead4759fcc81ec9332806bed9e46323c66388d0ccddde818c3f8038307b3ed943f813db5dd237d4c4fd9e126dcd8824fd6b983462652cf1dfff1ad8dd745b7b673a803ebb0c1b2b3f553a97f44c0a7936b4f8417db7cacd20961014390f234a03fb24bb9d4aed1bfd9b85a66028ad18d639b5c18c1550a5e8714ba464cd05d64db8f193deb204550f4bfef2ca6d711b3cb544872cb257fb87aef97cb4170c016fa043928e94c11befe503d672f742a570e17965b56b3da09923ce7e9bf637e825cd2791d31eaefa831b579aa411b92583d61d32f7c8bf8b51f3ada23c118998624a5467b1abebe90cb997f738a1e6d4fa8ae9dad069d7ece75074964f3c2065e353cc62bf363d66ddaab23fa680ae34ea4f3bac4ac029294f4eabdf5f5ba1362368c4f79930cd009469f5743104c2e2013d460f41262918ad7cc357edb280616a5fe038bebceb8d73f9d6d9527ed733e3d8456dd1093afbc4e19819a6e60678f3f0c13f22bd89f2162c1ecc0f8aaedf68672fd250d272c3cb5c900e3b56138b05061c2cb1040dfa0d23c2853e74a2e1a8bbedfb6c49f59a576f01ab9a2f6e54f61dcc2835e2261ec1368d17985de0fa6e3d5c577cb8b3708e10b68ae545e69f073a7663c3ce41e79a12c2a3e3ba5777989f76e6765221486e712c0f81b2bfa9acc3276c7520d2479cff0d7d7debcfc8b14fec4d8da542844d4d5915a57817bb8097668b887548c9f1c2e8edfe435b1d21406cc89a1ade434e4ebbb948f99d6dd1dfdb218dc8a2b41659f9e4b6ae523f867f016f7819ac7277c2e8e92535a806c51b92eef8c40b92bc4b100a8c31f459126fa38bc2c2b1181ab0f707d242b8832f8c0ba2d6936169efe834548f2b1cce57ad1db5c38f806ce49033d541f50cee94cff9e9aaa117f597228d59463925c1dd8f48692ef2b0c0ee5a1ab369dd6176b2ca6ee8944eeccac71f378304db887e7959f8c96fe07b1d0e20f206009622e47798df24da10cf9b924eff305484c64c5c81fb1d7b678c8206b08f291736bb85f5e1d1f782c96abe9f13ed84b7830df6fe280b2bd8f14dc7f13b82b8b764010e48a8744e0d2c985507b2170763e65cbcb3febfd2a9677c223141e5adb6856b5282cb48686bda32a1ca48e434799fbe338606ceb3d4ed9d8b9c0a399b81d7e4d00c28c74d00532eacb45313b4d5d43fb997711906229502097a7db6827c22732dc3edc3b04cd903cbbc422ebb56ca13c359a290f6b5ae0cbd36f7083200d9e15fa6cbf39a56362514e0989c16c82cf53bdaeb118068ec4546573e7e8f806d405e1d50a3d39d524f5f2aa5551fda747c189896d92a2456f3682225560d0a702d77db1d52b6df97d24000fa6daff9f984415e024a6c6fb61023023c9629b5ffb850d0b1082ad96085e32a6c053cbb97fcb3ea743825424a066b4cf6ebd95405d6cd38cab9c58dd5967a4010cdc68a632aa5eb0bd0269493028696e3606b16203d81dd920fa9cf464d28423fa0172fd5ce56fef84db125f8cafd404af82f18602d76955025281a2b8185c3440c8352302b02cb90464eb2b741966444f4152b3d7a53ae241440f7d492d510ad1d0a175cbca852ac0c6e19210676c804ff468c653055b993d2a979012d58243c2fd44f0b1124eec15d1b306227c30ddf78a553e85bc5a93da40caf3f8e085ab674cc057a198d4f125dda6e41d96da2ce1950f9546ecd76be7de9960cb30a2f71e816af783489371a1e4527c49972f28e423faff346f88e3fe3abc6cbaa266357aec98672fd5e4b794e1dcf2cf6181a8a81044ea28e63a209c6f284e1ad0bc04e9cdf6168742d816493d57a0b09df33baa0bd79c202297b929664d2734151f809a921ffbbc7cf108ddf4fc1deaf3eb5b4b47c48713cd787fab1e5e62ce5b23de92909a3a1882857738d8c57b503a0b5f0ee2c764613717f9cd6ddda53faad876ac4b1d7a42c2406048411b4bdffd70034053142b1b66f86aeb27d678d26eaef27be84ca4e9978a11c08be3199dab5c05db983e9239e7c17dd6498608919ef05d9280346721d74ce61ea838c33ff24629892509f8de7f61b79782d3d0691f57c757b70c42f41affbdf1b1281ec1229f4147c20b63021cab2d5c7f76c578fba9c257890094a5cbe3a7a58fbfc07be61a49cd99f2fc61a774f26cc82e1a826e612cd51fd70db69ef74ab07f69d9ce308064ff3b373a1adcbc71380b4af89678dd4701516d620c41f6b028cb42c365428dba9504192763d0840dac1a21fc022b19444cfc039203765458b186c9e506cc5b05e9c2af6549da19b5a5a85a1ad0b1c886b1fa6aaecd625fc7398685ae0a8fb52329c3a44b2ea41e273df41c5747ace203733422fd09f15041046623286c2bd5e02be0da6015dbe982d479d42f1e49c8d3b2129d78fe71c413cd320c9431247156b3bf4306f59e643288728a3e526aa2d3414e91cd7db124d2c5906cde488ad30f52c3b72e1bee7ce857d6fcc5c1da3da93ba6cf05165e8e810c7205538cd4736996e105a3b57ca990b92e8c8e85cde59b7241ad0a25417821a061debed6c81a74d1af03153b813138647f1385a256573637f5bfb79821bfd95f00bf8c95f1d77b7db2367407d4a739eccf2af4289f6142d523a619ed348ba71d25abebd48199eb792eeb5a4d29be0522ed28ff6d2b32f207985b0517d50305fd40f90b4f73404c374643d6ec981c9a11eecfa679246e26ddf244f9a74c7cda8292b6406bd7404ec2290a31d5838acaa7d05f2718e55fbe527cc0b33891d6a9a87b78bb2a618c43b5057d85ee76eb054c3df3652ea75dd84d2bb3b4a6e984516f4491ec72f8e8a42350acbc18b526dfe2af5d9fcf901013627277f20880800e3ec908704726bcda59abc20e8adba9e81a84836764eda33e41cd980671a74c0eb4f6f86bf07db09cc44ac165169f8a0fac025d1412cb4b3d049304e8169556e0464588f1e6dd50050c3240530e8e597654763d8763bb4020955c62ea00bffe866c1121868e065334019efc5655dfa67afec67b2942a80ff7487de25b963ca7a260a6953b81d3367b8e127402c7bccafe79b12cd2608a0196c0a3e3bd34dc49f42c540bc0dd2bd175813c057cee628bcb71bd3053547b6b6f6e7925759a08aeca84d5d838c55f8d54f5ac9078400a79b74dcb3f082efece1a16e3568ceb217609da551a7866751a209104a0ce24262532f7f19a7c3ec89be03e47e7cf3ec6c9573cdde141e04de1d6583eba222c69fbd7591aec97613d39c24f8b83da5a007185e15286bf7bc3ed7fbbd6d911140f52d7c199d806838c48fb49d5d9fe7f16e3a5819dfafe31f6d65e2a3b89ca5673e58a1196d743ab86cf86bf283b725198e7ece1b1a38caff90b677aa34d938d593aebc27cd3d1a755e4b607ab0959885bcea5527340af82e95d371f8ca58cbde67a9de91ca600c083df5e3b46d68b30994d7dec11ded33e1f2619930931d275c37696af3d6759b4c868d72f3a0204bf7a1642b2cd95d91671bc6edf5bcdcc11d2dd1c56cde62090590fddf4d582b695834dea22560c25da293d5d8a4e42a5f10e50d8332510a001b4ddad2e8014f9944b698eab80c2e909a90af058ca0f079175160bc483a3c7a0daabf15bfb6a6a121d8779abc3a0871bd18f2b055e0808e2cea328c966d38afc4b7f28540996da6ea2502f5414c881134c99a249795a01345e47911cf7a1edb692297dbc3bc13a12783813b3405f8eddedeaba91ffc24e0b4fbdede33a78536d3b1ccdbe015804375b6191d48f392787452b3183d50d8179f20dd7277f5d9598be43152b49818dafbd8b20ffe55bfe62d6e28244942f1212fc62a9430ef8ce67dc8443b5508279393e4c33e7c5e2d9cc510494b887671a701dd71e4ef7a124f87a20043fc4596b2d148bce1750b063c09e20f45fe8385113e0f29f06ab3f77a77f7b78aeeaf565ca7a692c9cbc76f5a1a1bd91e3323794a2691293cc54a8e3475fd7a5b9b1519d6a08bd51dd739d08da826e002232790ebbf73cc6b986040a44b35794c4d692d44b43130baa8dbe516c22c4a76259968a7c3a95a562b8d678922cf424e2ad73d653e97dbee3d277447d706f592d1cf5ea28e47ae819b1ccee45f0e3345e4044c34069f63ec26439f3f39104c4301e40e1ac6479db9401fbf98a93788e98b6bf0b44afabe34587f2d0d0d80b66a967edcacd4dec499243ceda40590ceed12f1090d49f166a37d259193606df817f25cfe23b850ff872df51b4a8d404610a70dd1768e2043d4432d04678a0e29e60a5350b2564723290a971a0b2ebbc52819a7afdac6b84e283791f0b97a90dfee800e031d77c8af7ca378422a6e05326e7c2b4b6026db21a5741d6e19c47fb82bebeaca4d6c1df160c6781557de24387f6fa9ad7829b2e129a0e71cff4cf2bbff1729e2763545354f8ae441af7b8a6ecd7309b49b7447f3c2c9a4f3ef386630b60bed92e20a7551142664272328ca92df3d6a62a5e1b4d5ed254a6f622c435d2a87d00b86d3df0759908ffdd6ba91ad8e8a5ec29de8f15c53c17e14417ba9e60da3b39432707058d27225d8fda1810f8028e90a212c5472d64fe7a723969d986983b174837f62820605d2e2cb0d1fe7af51d6844b11b1a305333836a95e9eb1c0790a1101df8ec631fabd152e1b40868532b3d463bf93db9231902446ad5cd441b01dea8a5fa0f18b0a6423fe08c42356a9aa6f908ee7b1b032d10d850a7d9ec418db13d350dc63ce8bc546f07e9340529e6cf3be5033241008044203f99b8228ee04a8bb54ae7d0e58453f309722a3b454032752efed4aa9784449648017d6c38d658b5c44ed0b3d387d107f1f4dbe7dea010fbd5e74cb242702f24f3dc91ca369696d5f065aa56528826e869b81ee39f989d7e9ba6dc52ea6996379646d7338fd54cdf952ed6676f33eded4797b2d38f629dcf428739c4845c5b188449ec7c104b3a9199b316a13b383876099418661af4cfa5deb0e82eb16fa9c94b90aa5e6670701df400be55adba58478e95edc3bfe00e785988cbffda72371d66adaedcaec05548c5c626e14305ec6ef90dd3f04dc9e3812fcfa72399bf787603e25eff71f0e1193adc7356642c98426d5159933a4f2e226a0df4a8225e0badb1aed58346bb10381a88b0f4d2c39dd048b1ea5ecf942107b7c4841512ab3529c1cf50f0e0a21afd105ae7b1c117d38d738cc2ceb2b30b6821f5b091bb66fe234fe75ff6313a4fd04bc55e785d11506bfe77081e33f095608f7a5a33f2d0f6a7df5cc7f2b6b3dfabf1e0f73ac032ced8eab7d0a28b26213e1027be749d433ffe61b366ec02fa482b7100932141aacf8e38cd68743dd5009657b57c55c55b65d9106048761b2186b075c459044e66cd65a219176612127eca51398e4c42934a1b2a04afa7cd7c98974b8908890e1730ff2bdaa7e4f3bb23f86b1fe468b97ee8c3edde6a97fe4cd08f8c648de9eea42031cc833d0f20269ed17586428e68c1ffcffe02ed75b2e2ed4b77c801ba94aa742331b1a98a0fbab27183f1bbf9f8c7742ac7a2942f599439451e03b40098d0858f7987e967c75b99a12385a513f20efe25f77f067983111cff6a85031f759424487c328b804ea00257debd21e6e34fc0dab73acd202100835ff5b54e2145433a6bf0b40eee99d8abdaff3830fb3af2129d1e1cd053b248cfde1be7bab06a8f9969eed3f380342492544f21e2e09fcd650d11ee5f628a2444476a010b96a8e0bb632a0edb448b14a988064d7f6616bd70db770e1f542378a4100144d937a0fb0a97744755cc604c963798dcfd037a917cde04e5ff6c76b108bbd89b3dd9432efc463fbf8b1f3cb3d760074d51ba43a1a662a27dabbda22e913c8f77ff6dfd543a489ea8c4c54f86c7938dc47926c7f6faad04eb6d9ce739b7352562c168937ab02e8aa6e59af9eeb523e9d5c8bdc8b83373aca84e2473583ddc9023def3b0e8e55b4f8a864f3804174ab011c283c5f3decdade382ca7068b46636d260667846b8e046d5718b69139be179a47875b37badf1ff30a8bae26ab51470b02a67ab3ca6bc48529d8c292a23447fd51b2c7a8f2bbb1d406cb3e7f62e18db54fff0b370aae1cfc9a8c26515e3d6464f632a3db190ac20497525bf6d126f5062a11d39cbe4c6612ab07a584a08b833c701efa555bfbeddf4415e6012957dbb4bb31691f03d2c14ddad1a1beb40628ea466e1a30b82596ad828f835fecf72f1cae69ffcdbe708cfcbd29d7b361bc9575472bd2e54232ed867cb60660fdab6eb984b1fb9ada94d022dcc131712c144f36848b631cc45a2c814a86a750ab59c41c0a8e28f69a51ea652f488d73665241ecf99a8fd65fec7d98059ac072bd25898fef812f89d322e925334960f81f36f6ac592b0ff02cc532a5c4f6ca423eacf4903d5801ce3acfe6fba58f73a46808967c5c00923ebf85860be9261dd906c626c4457f97c41c30e86a5a38aba1838a989caa6d7f1d6585b0a2930b5d67bff208283a9bb8d7139090135aa77ba781e15a81fdf2d735b43dad2a280286f88b659a714b0618301cf36d6f3f556bcf234c907fef614b1e8bfa9f00623745983f4f7f98ecb69c14095db24ff7fa03a5465e963da88fc883fcb4b4a177a95d28f5cf59926d0281e31c7645670d57dcccdb9863203801cb552d861feb2550858514f7779c4f14e513dd640e51100f3f81130f4a9765fc6838ec661c7e43743bd47eac7495ce321678c36c63be03de04571028a78ab408813a783d10985d6134d0edaf4bf991035d5925cc6d047c998a2b62dcc352b0632c54d6142c62dd518db51e9942a59322183d4a1d13e220fa89a1a368b48c764520d7b2262df32cb3428349a8d02aa834d2eb5ced713ee5ab133df9cdfd8309940b5306ddacddb877d371ebcc9f1105f1ccc2e9e4c1c0ae687b9b2c089f69e4a595deff6e080e419522eea3d42f2df24a75a68072c644c8ec151842417916022153d5637628570c4a9a23e5dd2325b21e5f6ff65b696bf7fc52cadc7a056ae5b96031445c2eb0b4473fd1e66bf6b8ea6dc2406cdbd35dd5ab55b921d5dc702e71531d148d16d13ad0c7f24574ad742f8c2f95a61ba544d42c9cfbd93a93e8053ba3d0d5ffe4f8ab4423d476031280471c53f75c6c73f5d819a8a034ee5033bd39d29a5c2c38e317f190a126c4b6b4c8e23d7dc5c9be1766aea5eee501bba52a9387bd3558555387237778229a2094f5cc3b8a06b6562f2fad53237dbc1d24bdc54da2e2775b796592b4b95c9115e837a45da2ac9670bf14be5cd15eb44e24febae2f5b51ab899d714011483c8b3ea610afcafafe6fbcf56d3def17a6b3c57bf57e437be80f7fa1538d69ea0024163de30f7be951acb1aa75dca1fe3369bcde07539291958769db1568b55c74480206197b406003ed53d2a560e1774e62a7d7b88c41dba82d719428a0d8c60858fc2f8ddd8c5a5c38e0403565abf43e8a4a79523494978c3bc0978554da4681a51a21f82712179e8be5e635340afb23fb828aa4c2f8757ad4abdcf01af65b35b102609ee5f3f5bb1530e33f3c28686d1aefca7a550a4fbd6236103acbd3da5613b1446edb1ed35d741316510dce6d3e82726c26057dea78b7f456d85ddfba104f7213a50b205d61cef7e2522c5111e60998dd76a2b7c7c5df22170046861618de4578475f0ed17daf8fbdc4c8333e23bb746fa0b8e9290c22525d91b50f83c36e70dd41f70a711552ab9e173f715ce3ef9e0d607cbc96c91f0257dc7890a5d9dcf1677e519a238df15750abe79f7550b1e7c1f73f04f116765c7e2f67fd9bfc4975e0315ece7073074e04966cc9c513296a4991425cbcb56bd275ce80382bb1946df3ff0a33ff63421eb60aa596a87487c71f2d3436318f9da1e4708b1e6285a000113ff11a7d4ad9d6d71b3b3d6e0d04bc737125a229ea1f7546f4def0af9bc4e87b75aaec847427d1285dfc2b09689bda75e413322ea7e8b714cefbd7a960a99c27028b888a9a74fec983840f44b30b92ef7ff905010b5d42442d8bdd2c30fbfad130050a91887cda5ec795f300a13c54349b22fa3870a3941e4e1bc01f522f350896ddcab886c461712f762a4dbe13dcccf155ea70ec5ff63c69a1bc79ee99a26e68ac7ad5df848af9cd43b0c1a756370f013f61a6b7b5e791317e36cb263e1be6fc831ecf7f66dde7ad14a771855396e538b07c6020da1d75089381bd911d7112666381bd8452fd194218d05052ddb2b08b2d5c990139915d07b524af66b410d8ceb4ed62875b8b018bd5888b46a7f2c47c4fb3d07137ad39b717f336be407886b6bcae3aa8746686405dfbe4eaddb78f6e36cceea07aa8ffcbfe5849be30adb6849246a1da81ddecc77adabf46b5f84e70d9f675a7b594522b1feeba8ee435098fc92fdf9fb27d8661a1148417045d79e1cc3ade586dded17e93cc0d4491b072b3e92453e742d4f43a31d718f755a341e6a669b79f18664bfd5e05ad0dea6485a1f2f078216c72dae972ce446b820ed26481a37ff68b09f753d599007f1961ce6d52bf7e6bff473e47d23809f62a237b523a566c53a3209e8caac973ba5b600c466a7c911a0696b8a37b4baa8739fb28bdfe062ba7491208d8b1883493040157a5ddf6f16b9741f78a323b84b30060b22e884ed6eeb86f915aa0d36984f640445391ebcebfc8afaa2eef5c2f7ebb056dd48c6dc4e07d42172cfe07effb742c4e1458c8b6309ce65b470b0a8df8ff4e553023bb6e9b6aa5aa2c6498c8d649821d2c4358854c81d01b02cc8869f84f07d2112f529d312c92c87035ff7952ea0e7f8f26e23eefbcb6ffa6ea84d6c942f06d4cc05df26fd2f0d53d9d1c5d603722d29858dd6c3b14db1e4592506bd95d07819c890c72afc253071f6a76714d54b55064deb4b2c7ec97197e5e0da3c2d97221d899ae27249c78cfaf967ecbf247aec24105219ac74f43eb393bb16159b4b38bb78220450d1b01a059adda6403e6d3e26886ad188ed9fa1b0a907d5b6388783dd3b5df41beebff2e7f058d0c3ae4aab601043e529a2923492f0a947b6595bf6e67a3673e753ec943ac7c38c0a9c20816ab6155d03d39ad99111f9022b4a4c337a2a28f7799ec991fde9d3584b74e927cd31a31d32d669a8f69a0fcf5d6c1b08721d22833a47215ade171aa5b75b640ba7cb328193bd936c7af149351ec6f7cb32adc600abe961f81edb7da49454d9e91a0d46cf7c9881fb01a95a6896169e76ca33765bb7f8f3ec33d7d50656bb289548f9c5c1863afae8407c056cfd6d8522f2a68616c208321b477b825b13e28e3425940ead4772e167bb7da40d23fe20e407d02f1c220aa60bd00723293c6d3796dbd3298e1fa57e96dcf0ddea24b66f7512ba314d1ae4662ad881d0c52ca55050f080e15ac8159f4a56166639550771d9a3673c5e301c8b555232d79a63f0b64f947a553cfa9e638a337f92442049169fe96b30e4899cdef0115d100ea756400018ec3a108cce4addfd16194ae2b1df02629f508dd74d897889551a66032441cbee29aa19c1e691d9ed66e6446e7d6ca428246f83e8817c97b7a34e3b2a7a3dc649d7991cc0cdd7996c7964bdbee43586b4d804f634e9918d4048d60504b02b8489c668ff70fdbed1f10d8866a845816d3d616ed56016ad2984a95969ceced20d217e8357b6018d4b342a97cde72fe453fc5c106f7b21593fba4c6b91daf4710a6797efd9c3d66dead977e06559ec94eeb6e99acec0d154b742bc87ad00debe0796f1ae150f0cc90649df0ec088eacd0cd52e254b63d20f6f90ef7e9f758d43392659a9b2304d4b468cca2db62b9a71871a77ab0075405e8987feb5ebc4f6d325b48ca8802c3f4539b35932e22314d595859295cf07ded1398047a3037f0c223ef49e3092c5b831a3be396f7a6a7cdf905c20732528b909641947a29c3c92207c845fe4126e67597c36d006b8118882e2a9c0fa79067015af76328f0fab725a27c4444221386a825b2cd61192dcc8efa59767d625aa4b83d4f8d66df3540c735c3ae96d9e434ad90000a00b564b5b080139a00352cb8444e84719d81fa315791e8460c92683773437f9a21c313bcd2c9720910e118befcb087fb65b3a55280f8d4f52843ef039895f831213068f54753292c2a2538cc134ed30e109e48227218ea2c59ad3eeb4290fb0d85774e8305a0eeffd7718e15b37921c5a0d4e93b05c154627c0012c5193e25da4549e228ba6a2ff9b76440d734f07ac04f8aac6e3b0cb30b1d2f93ec53e81cf6b80e6c401b2dbb550565dac233eb2e04caeccc08f0cb5303b9e39e662176857fcba699d7a683b46d986541a4c6973b90e661c290b682280104644f9f100bf949b66e5329b5234a5f6a260ed2041d476b322eb6f81144199957262098d7c4fb4744d00ce57a6abe692a1e3f792ba285c15ca57e139ddcf603a6c290ba2c24c66c4ff513041d3f74b449ae0c533155e33d44b3f766518950ebefb619f78ff77226481e1fdbf412e155bc3891234f7c2b0cbf0f9860eb9f45dd39ddaa1eaff025e9ad796dcdd55c1c8b0f0229884b1ba407c8c5229e5d45f1f8076e50288f6cd090c033bd45f5a3238ef434784349dd89fba8019930a468e33bab433ff49fc3d5bf41e4aaf5e7ebf448deb1cf91b55eac923d873572a3541033ae74e935c0133c41929ac5fd3b08080efcfc2a56b180cd8129c3d589305c22e9a5549828ca8febeae497e9af0cf198ac35746563c92bd3b4786254c82d2d515f5c3389aeb388e26015edefb8504c346b08e4dce799a6be514c8f47ccad44d1421052dea2f83d80a57d59772c3a86582712e4ee58d735be3aca7b95836c8f61a921a1895d03d51760ec2e415979e17b9451c0bad503d46a935a20922fe007a4704e9f5289062694c1e29b303ea8d244e3dc0ca4139841e483186c97074b8e38f2126738a955fd13a821900a50631354ee175962657a3214c7d2588e10a9cca1b70cad49e25d3da835d0e8fff0a75dc979726ad267f04288a6605df70a9a98cabeee500c1b45dd626e3dbbef5b04a47da2e88cfbdd02bed511461fddbe71e47f1c0f5eabcd36035ce5efa91b7b1e49227ac4eafd499f16e337ec2707871ef540e0f124a930057d9f446b38e9d4c6d8d39c3609800375ec678206151ee2d76f1192a91756bd8e3f940b4ada66fc58892f267f0f61e73245f3643558ebcd137f7b026b3605fe8cc1534c100605370bf829ba16ab6d5bcee28bbcf3a79871e26c179944f2d8a474348aeeaa94c8ba641afa5449fb308fdacba2bd8f78a482966c6c02ae094709962e8ff4e5d6e68e41e0bc087d5e1cf71ee0d245c986807073ad88137f13ec1ce664c53e9f1323ca77c4cf217078dc731e8fcd2447ebb7d9149d51f3e564bb13550b0a5bdaa33e63fc59610ca13897881ad33f539a537959d901e3ba4fe97ef0a484d8bcf1236623820b7787f726e70f690d6f8781f48e6899d10915b51864a620e02f93446a1e2e950eb72e2766faf2d5417830c4aea2f17aef34f372541996ec6025a056f1e61d4adbb29028b7a1933d84f3bb3c9d48bb3b19de5d460d52f8fbaf09d69db15382cdcfc800940a01d80ff64847c3a4cafffdc43d70d82ddc7f03a1b25a44f8ce39f687441ac582f68ce25c45b978336b2363099348e3c0356e7680e0d6ad853f061b10295c6be5b18b432c4c3bfefad7c6dba450a95632f12ef2d54737f02afa30193e16bae82a738fd4bad1ee5a2120dde6492d21f74ec9d77d6022cee6772dd7718e5377b06f01407d1affec4ad9071c4852d6495240cd275e4089529ab65417e807ac81d08796fc7bf9715ef09665d9303ae518075db2e2fd4b690b04c9b7e488c82ffd728ff3e264e5bda8c77638a1260df1e452e7e4c14dc7589c7a5c5831fc3bcf59087bc608439f7a3dc0ce779e09d5c19724b8c7c651380a09b48a9dd52a9a794a464c134e8d83136d6345c2a6e9d8ae87105af76eb07a7bbc00a5307f3cd164ee7e2ec7bcf0abf1c1a72b816d1023c0c2605717675d0fc7cc4df99f38e960a45f776d1c49aaea90fc2fcc32aec90198806a288254e536457c84581bffd9650388dc65e852574ff1eb975890c297e6b450b0937851c1826eeb90e5d2bdaf565d60264f111178688708c97a7e962d24e71eff8a2e99b7bb8e14292133b2b13b9a68e5328cd45cf907513ce47f3ce9614ea1a47ae50c1b162a118794823a924ec039647ce9902d3de1f28ef6849888f4e9ab5a424a4bf3c6a19a34957337af57a2401c0fd002ffb914f3c693d8f2309543e7eddd2224bcb445f5c12710d68dd1960f7ea262e86eece340019c49a013a3b7571650dbc536c4f0e740ffa21b3ecd46257bf359061aa2c59916e9312077b85f1840033569c923393ea7471f4241bd5ee2f23d9ebd4d73298de5e0c5785c6ecbb8b0fc31119523625a0d92580bc35674a24f6e60de3c00d1e484befc52a6dce04d53e734bfa04a74bd3c0dfce63c87b6f257f299de1fa8f5a0f55b9b7d5d700cc2a137f67f9cda66bea8586c18a02bedcf8fbb4491a1868ee02cb06147d169bf1a32be42c7ad1f450f562ac441212f9ae55e3f06cf9f94a2c1f2d990d685d7a2c9c872fccf81cdaaed818aabc4520440ffc2f5f0013c7157016389412a6e6c1f69985371e2fc9817a3f1733218bf147daa246e0e7ac8ce5b6c30cd0997ecd6b58ef68c61c658dc589d5e4432fb3afcccc34945774f872ad21663cf3560d9a496c236d5809c26736f0c0eaf0e96f3143cfed10a2ea55b5f1a3c9ba1c1c3d2f7a04574a654bbcd122538a2329eb797075faa4527dfd09904dcc01f259308fd4e750cf1304658b9163058e73204eba322e143e0d860936a9d4aaa5cb3d6b4a5e875baa93b1835a424091ef33bb505cd82c14bc1f3ac7cdd1266214a1e5f228dd7ed5c161bd7fa195ebffe4bff33cdfe3b1fc18701be7f68ea6f36ca557f7717478a7a11d8e5238bb40e6406730593fe796ec56136a18e97b93143b9eb0be36f268dfea221c906613bab556a744dbb9c9e7a4f685cce5135d53b1a1f5e2ff36165297837b2d99124ea2fcdda75fbc11bebf89338ad6255bfbb40a6ad83b2f9a0f31d943febf944196bb8bd8b66eaaf74bcf00bf6aef2852af3314ef2998c77ae8dba95155d977d8d12ad272edca74af5de5624367f5f0b50e83be77fbc3b987b0e451fa9a7b5b191aa29a1ef573fdcaa8cd4ab34ccc2d9ca1608ea45bf1c2d863e14787cfb69e25501338b0fe9dec91f87aebf54287ab0f844ffc22810412a0a84150060f0a1dc79acf306e57548936bda086247f9abfb11e403ba06f39ac0ebae99f43611ea41f8a80cc5d29478b4ba0210aa7b4919ef3efb8bd41f9a29ed9531cf7f5eec47356f8bd9d026194ce53d2cd4013f6196fd40128ab01e2bc4de0b0e0946db8e31b12eab03dfd117d2c6fc93fce372f16e61eeb115de501854c4f704579dfb02ee370ea881a6db9c5f2dcf2aebe309881bc30e4dcebf6f790c0e8d4b3cce7fa9de10e2e2039009a9c3ec0ef69670cd7a7526358e0ed3b654abeff56de71b70abab1edcb5d1fa4e5dcc31de6ed8963466a065be94647447db588dbc68b340e4782abb0ee1c9f628c4f4a4f1dfd1383649cec753b4a8aa691ae638ae2fb2645d01e39492e8d7e74ff5cdc5f72de25b1395bfe886f14e18a997667b4ebcec9e02c9a0d510aad1df805a4006c803d2b6d507e2c528ab8b283017f28e5c4a671ca7d457ae58e4305f66bfbac092e0cb30191dd475fae2f0ebc0bb0d4fb8598b78cc46e3b61f877b2ea2029cbbdecb133db99db8c40c4c591fc52a102a6930d03a0c93e1db6b8226bf61e6c0658ee6cab8fb7ee84ac26193026ab3df698c10e5bdba5a0acaec9a32c2c6cbc0b804f19d5ee29c66825b416cea15f4e9ddbd434ce5b1dc185db9f61f136b825c6f8827efb1d8c324fe1fb3516b1265b5a22fc6bcfeec5f8acecb8961ace647099fcd0003fce4c60ed51de301ea7ff13e8c098a7e2dbb7eafb81d77b659b4e8850f048ef095cdcecf930d66490992aabe284caf6fdddcb8914e3f8aecec127dfb97c23982c562469792e9a0cb0232d970c9d626c116e30a087e65f7f7bcde075e4aed6c868acaeb2296a7fc063fe424724bd853be8b3370ad1e50f1d577c34b23d943c3e808f5af68cfbf45dcf016eeca9a8d7fdd34ef899104a66e4b59453afaaa3a7667698cddbcfa142a65b968addaaecdbca642da6980c893be098442ea011688766f6e9b63a550ed36181ed18c8841a29f3a8635a4e344fcb0549bbe7fd1a9415d73f35fcf0df2f9bb849496f3ec226ddec95eb87bd3b5656a63e3e98b8b704979e1a3b9cd6418ede84dabc842443cdd2be529ebb1cb288c1a8d375929d3abee2a0a3351cc5058324f532f3350a063b1596c6085ba2e8b977bb962158b9473e4246c1a7c19b94d2c7f7847fc3c0b022e8c4b2a3213596935f3458171153095648abab2e633196fda15f6fe93b9facc5b740806bf951df00dfa0251e3dca4b0aba3c8e11f68bcd5e056f3952e41065aaeac02fae1618d60f2019402c05e6617520f830c814c9ac29737153a23f81e0e30f716b4bc3e30177366fcb00cfea299c4a073384f208ca018717ca799a62cea6424da6fdbce9346af6a1828a8162137be4adec4dd5c710b68f22747c591aa24aa1a790dc918701f98704cdc28a4deda97fc98f7c203ec64f308e5e24b37fc7987b7336619e857550557b06db949b2dc854d12a4677ea6705e58deac97e9ec1a08ccbdcbcb79898913ea2cf4ddc7ad5d0ea1516b307c4a6e62c304dff2ab7a4b338312523d751a200aa93e9662466a376d4740b3de1c36e82dd5f8300e1381d68693371dbaec40ef9e563c12daa30d20aecceac5b2bfcb5ece0a65852b2e70112e8ecef82b9858f39e84de929af94647474a7537fc552265ea6e9cfb1ab06dbd4d8653a0c5ec4675bdc85d912e85d991808dc4bc65c10a061f79c5a73df2ef719afe7ab8243ba29a8879ae2007dac60aeff1d1918128bffffa49ef9a2ab11af13295139035236df9fc0e27ac34e41075af27af5f385a7dd9640a71b89e527a1f14e5ab4d45f90a44456af4ec11eef7e35ae884b02714d65ab1ee84a6fc6079bed37f5c9d69010d4303ae3ecc422128f79081fcac9e3803839238fdbb52253ce1bb0b860bd3f41735a34a11a5a64463c1e9d3234326ca9272e908aef969d19352c233ee21415691ccb0bfa339e62dc73ad1348524ceb575b5c8a372b55e2558779299c7e41f9ce79d7c0a3d5cc2e4084346278dbbee766d66f2815d994fa5b316d73ccc9e6cf9d305e105a1339338a2db39d3341328082ef13a69c429660ddc60ee9b009deb99fa631428c475209bfcd40315ba0dde83bfa0f3392c2867ee5c7c4e1675767a246247687bc63b22f2a224e5ec87e2bf2c1260517b3ce89437ff4e5544e26bb40137a2adc61545139d82f05bfbfc4e1993a93d4fbc728398b7665842048a5acd17b5d628da74b5747eaba11bfc5f12c83241266ed6d9e67675fabc9f81e852c7b1a1030ff4bf2aed40aac808ab39b9dc222300ebc2fc3e03d3cc5e62f2c3e3fff4ba23aa79eace3185e261ec73c5c8b324b814b748a4c3febc5488f8cf04cc1367d752531c1bc572690a3a06598d2d69110a4b926c09a1493e9ce9f292525b7f025f79dc793febd0a0ed2b26fc98cc3533a8acd8a41ac3582bae389e399cd3e3b94441efe744e3853b05d4d318582d65834ea28332c6aa32adcf856d665cc0246676f57873f727465cd7325b15e68667caa60e59eded3df0cfa9c3b2e0a7b536a89ba675851d79390ec8c2c6d193197db41c6d9b6bdff3d9a2c41bbf1896e9a97c3ad63a4a26864503e95f20125e5684a3ea5db9437885a969a3802e827434fc70f2f6c966a94d3de8bfac7e2c6e28e89c5302cda49a81d5a5406cd895a8b40e54cfbfb30b7434dda96355ef50ee85f2919bd7a8ea65f84e12b9aaa09927dc55b394acedc604d5ab23229a4726e08811ad4e10879eac31d312dfdd77db2b39cc6de63cf3651e4f2608b07f777b5e0c069ae8142a08b5ca95ea0bb8887fff3ac8843aa990b94c86b90bb93c04d1f843d6b9deaf8c427d87d0799233a07fcfc69319c3cb07151bacef04b32b617cdba504ad28d68bd3feda04e01ceac425bbaf4f470954702fd516114c84970c05c7635f3cce73c3164e23f87616d42c57ffd3d782dbcfbb9f71b1eec605f355f2e3e8309ac4e45b915a50acc77b34f7c18122c23aaf2f75bf123404a913cec231910d2c3484e06412478d32fb6f40e3b38536f7896132885c30b6c42cc43fcb03327c2b277eb6dc3c836d1c2c6b66961aaaa2b02c26d3a2dd62d035fac1cd94fc0a1532cae1faeb6c0c2d1bf7dcb2d1375f6baed8939b4cc5b568e704d8d8598c2749bb711f007aecedf99daff7800390e0c51fa63a17fec4a70063ce368a691881c85f7eed3155455bbb1b6099831a60912532a74320eaac9c1d73ceb881b3f46eb8e72505d7465b8fb9d76c6028d95cffa141882ae1c9102adff6390e07b46151fc2b500083c834a084bde17a6a640f55ee0be3b7c0cc5eae1ea56aab295e23e9d9a239f26de8ff6900aff9bf78bfb36b0c324e9103a66ee4cb6b99921e3d4cae9c2599da7917a1d287604f24ea55acbba1852d5830ddf4bbe78979b9b7444d9f16fe9b1f7ad7cfaad62ddcaad1fca59d7a03483f4bbad3ec002beae6a52cc42cbdc2e</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">项目场景</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css"><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/03/20/%E9%A1%B9%E7%9B%AE%E5%9C%BA%E6%99%AF%E6%89%A9%E5%B1%95/</id>
    <link href="https://www.sekyoro.top/2026/03/20/%E9%A1%B9%E7%9B%AE%E5%9C%BA%E6%99%AF%E6%89%A9%E5%B1%95/"/>
    <published>2026-03-20T06:21:26.000Z</published>
    <summary>从客户端发起一个请求到结果返回，到底哪些地方可以进一步优化，如何结合业务进一步优化，优化时基于什么技术进行。 这些都是可以思考的点，在这篇文章中进行浅浅探讨。</summary>
    <title>项目场景扩展(一)</title>
    <updated>2026-04-15T10:26:42.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="AI" scheme="https://www.sekyoro.top/tags/AI/"/>
    <category term="SKILL" scheme="https://www.sekyoro.top/tags/SKILL/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>Vibe Coding自提出以来，就有各大互联网公司、模型厂商以及开发者响应，提出许多基础优化编码。从模型的训练Function Calling能力和上下文长度提升,到Prompt技术，RAG增强，再到MCP统一协议获取信息，再到最新的SKILL。从Copilot,Cursor,Claude Code,OpenCode,Trae,国内外各大公司和开源产品都纷纷自己实现了相关协议或者提出了相关功能。这里就我比较常用的几个编码IDE进行实践，使得能够更加成熟方便地Vibe Coding.<br><span id="more"></span></p><blockquote><p>省流版:个人推荐vscode+copilot或者Zed+Opencode</p><p>如果嫌麻烦直接上Trae. 此外还有Windsurf,Qoder以及google antigravity等等,这里使用不多就不做过多介绍</p></blockquote><h1 id="Vscode-Copilot"><a href="#Vscode-Copilot" class="headerlink" title="Vscode+Copilot"></a>Vscode+Copilot</h1><p>事实上Vscode可以搭配claude code,gemini cli以及qwen code等等工具。但这里主要还是使用Copilot,因为其与vscode搭配最丝滑，也提供了最新的功能体验。这也是我目前使用最多的AI编程工具了。</p><p>相比于Cursor和Claude code,vscode的商业属性没有那么浓</p><h2 id="MCP"><a href="#MCP" class="headerlink" title="MCP"></a>MCP</h2><p>个人觉得vscode安装并使用MCP比Cursor方便多了。<a href="https://github.com/mcp">MCP Registry</a></p><p><a href="https://vscode.js.cn/docs/copilot/customization/mcp-servers#_prerequisites">在 VS Code 中使用 MCP 服务器 - VSCode · AI 代码编辑器</a></p><p><img data-src="https://files.seeusercontent.com/2026/02/22/co0Y/image-20260222152350682.png" alt="image-20260222152350682"></p><p>在扩展中使用<code>@mcp</code>搜索即可下载,在聊天中而可以通过<code>#</code>显示提醒，或者直接进行询问，Agent会进行查找</p><p>安装MCP可以通过npm,pip等工具进行.</p><p><img data-src="https://files.seeusercontent.com/2026/02/22/4zfZ/image-20260222153755886.png" alt=""></p><h2 id="SKILL"><a href="#SKILL" class="headerlink" title="SKILL"></a>SKILL</h2><p>skill是可复用的经验和流程，一般都是在文件系统中创建md文件，包括例子，流程和资源等。安装就是将对应md文件放在某个目录中,或者使用<code>npx skills add</code>进行添加</p><p><img data-src="https://files.seeusercontent.com/2026/02/22/z2eX/image-20260222151813587.png" alt="image-20260222151813587"></p><ol><li>SkillsMP（全球最大）</li></ol><ul><li>地址：<a href="https://skillsmp.com">https://skillsmp.com</a></li><li>规模：<strong>14 万 +</strong> Skills，覆盖代码 / 办公 / 设计 / 自动化</li><li>兼容：Trae、Cursor、Claude、VS Code</li><li>用法：搜 → 下载 ZIP → 拖入 Trae → 自动解压到<code>.trae/skills</code></li></ul><ol><li>Skills.sh（Vercel 官方商店）</li></ol><ul><li>地址：<a href="https://skills.sh">https://skills.sh</a></li><li>规模：<strong>1.25 万 +</strong>，一键安装、按下载量 / 趋势排序</li><li>兼容：Trae、Claude、Cursor、Windsurf</li><li>用法：复制命令 / 链接 → 粘贴到 Trae → 自动安装</li></ul><ol><li>Smithery.ai</li></ol><ul><li>地址：<a href="https://smithery.ai/skills">https://smithery.ai/skills</a></li><li>规模：<strong>1.5 万 +</strong>，显示激活次数 / GitHub Stars</li><li>特点：社区驱动、质量高、可直接导出 ZIP</li><li>兼容：Trae、Claude、Cursor</li></ul><p>📦 官方 / 开源仓库（标准模板、可复制）</p><ol><li>Anthropic 官方 Skills（标准源头）</li></ol><ul><li>GitHub：<a href="https://github.com/anthropics/skills">https://github.com/anthropics/skills</a></li><li>内容：<strong>16+</strong> 官方示例（代码审查、文档处理、前端设计）</li><li>格式：标准<code>SKILL.md</code>，Trae 直接识别</li><li>用法：克隆 / 下载文件夹 → 放入<code>.trae/skills</code></li></ul><ol><li>OpenAI Skills（官方模板）</li></ol><ul><li>GitHub：<a href="https://github.com/openai/skills">https://github.com/openai/skills</a></li><li>特点：通用 Agent Skills 标准，适合跨平台复用</li></ul><ol><li>AgentSkills 官方索引</li></ol><ul><li>地址：<a href="https://agentskills.io">https://agentskills.io</a></li><li>作用：Skills 标准文档 + 社区索引，跳转 GitHub 仓库</li></ul><p>🧩 精选社区合集（高质量、可直接用）</p><ol><li>Awesome Claude Skills</li></ol><ul><li>GitHub：<a href="https://github.com/ComposioHQ/awesome-claude-skills">https://github.com/ComposioHQ/awesome-claude-skills</a></li><li>规模：<strong>300+</strong> 高质量 Skills，持续更新</li><li>用法：复制链接 / 下载 → 导入 Trae</li></ul><ol><li>AI Templates（aitmpl）</li></ol><ul><li>地址：<a href="https://aitmpl.com/skills">https://aitmpl.com/skills</a></li><li>特点：<strong>254+</strong> 精选模板，企业级配置、Stack Builder</li></ul><ol><li>SkillStore.io（中文友好）</li></ol><ul><li>地址：<a href="https://skillstore.io/zh-hans/skills">https://skillstore.io/zh-hans/skills</a></li><li>特点：中文分类、国内访问快、Trae 友好</li></ul><p>目前能用 Skills 的主流客户端：</p><div class="table-container"><table><thead><tr><th style="text-align:left">排序</th><th style="text-align:left">工具名</th><th style="text-align:left">是否免费使用Skills</th><th style="text-align:left">推荐人群</th><th style="text-align:left">技能存放默认路径</th><th style="text-align:left">备注</th></tr></thead><tbody><tr><td style="text-align:left">1</td><td style="text-align:left">Claude Code</td><td style="text-align:left">是（官方）</td><td style="text-align:left">所有人</td><td style="text-align:left">~/.claude/skills</td><td style="text-align:left">标准制定者，生态最全</td></tr><tr><td style="text-align:left">2</td><td style="text-align:left">Cursor</td><td style="text-align:left">是</td><td style="text-align:left">写代码最常用</td><td style="text-align:left">~/.cursor/skills</td><td style="text-align:left">几乎无缝兼容Claude Skills</td></tr><tr><td style="text-align:left">3</td><td style="text-align:left">Trae / OpenCode</td><td style="text-align:left">是</td><td style="text-align:left">追求性价比</td><td style="text-align:left">看工具设置</td><td style="text-align:left">国内用户较多</td></tr><tr><td style="text-align:left">4</td><td style="text-align:left">VS Code + 插件</td><td style="text-align:left">部分支持</td><td style="text-align:left">已经深度用vscode</td><td style="text-align:left">插件设置里配置</td><td style="text-align:left">正在快速跟进</td></tr><tr><td style="text-align:left">5</td><td style="text-align:left">扣子/其他国内平台</td><td style="text-align:left">部分支持</td><td style="text-align:left">喜欢网页版</td><td style="text-align:left">平台自带技能市场</td><td style="text-align:left">有的要会员</td></tr></tbody></table></div><h2 id="自定义代理"><a href="#自定义代理" class="headerlink" title="自定义代理"></a>自定义代理</h2><p><a href="https://vscode.js.cn/docs/copilot/customization/custom-agents">VS Code 中的自定义代理 - VSCode · AI 代码编辑器</a></p><p><img data-src="https://files.seeusercontent.com/2026/02/22/x9hX/image-20260222152916775.png" alt="image-20260222152916775"></p><p><img data-src="https://files.seeusercontent.com/2026/02/22/Ftx7/image-20260222153017215.png" alt="image-20260222153017215"></p><h1 id="Claude-Code"><a href="#Claude-Code" class="headerlink" title="Claude Code"></a>Claude Code</h1><p>claude code可以作为扩展集成到vscode上，也可以使用终端cli工具，免费版每天50条消息左右。<a href="https://www.runoob.com/ai-agent/claude-code.html">Claude Code 入门教程 | 菜鸟教程</a></p><p>但其优点是可以更方便搭配其他API站点和密钥进行使用，例如Claude Code+GLM或者Deepseek,Qwen等。</p><p><a href="https://www.ruanyifeng.com/blog/2025/11/doubao-seed-code.html">国产大模型接入 Claude Code 教程：以 Doubao-Seed-Code 为例 - 阮一峰的网络日志</a></p><p>配置后可以在vscode开启终端使用,本质就是更改api endpoint和api key. 目前可以搭配豆包,GLM,KIMI,DeepSeek等适配Claude Code API输出的格式。</p><p><img data-src="C:\Users\proanimer\AppData\Roaming\Typora\typora-user-images\image-20260222160313766.png" alt="image-20260222160313766"></p><h1 id="Cursor"><a href="#Cursor" class="headerlink" title="Cursor"></a>Cursor</h1><p>Cursor目前免费额度是<strong>基础代码补全（Tab 补全）</strong>：<strong>2000 次 / 月</strong></p><p><strong>高级模型慢速请求（GPT‑4/Claude 等）</strong>：<strong>50 次 / 月</strong></p><p><strong>Cursor‑small 轻量模型</strong>：<strong>200 次 / 月</strong> Pro版20刀一个月，如果没有经济压力还是推荐直接升级为Pro.</p><h2 id="API-Key配置"><a href="#API-Key配置" class="headerlink" title="API Key配置"></a>API Key配置</h2><p>不过可以自己配置API key,包括OpenAI,Anthropic(也就是Claude系列),Google(Gemini系列)以及Azure等(Azure也提供了学生一年免费服务，我之前也用过，很不错)。 所以完全可以考虑使用第三方中转API key进行配置，中转站有很多，为了避免广告嫌疑，我这里就不提了。</p><p><img data-src="https://files.seeusercontent.com/2026/02/22/ip9P/image-20260222142904526.png" alt="image-20260222142904526" style="zoom:67%;" /></p><p>注意这里Anthropic的API地址不允许重写，所以如果要用,要么使用支持Anthropic官方API的中转站，要么使用OpenAI处的重写BaseURL进行配置，可以自定义添加模型(但目前免费版只能使用Auto,因此免费版要么只选择自定义model,否则其调用哪个模型是不确定的),配置好后即可使用</p><p><img data-src="https://files.seeusercontent.com/2026/02/22/n9rA/image-20260222145428339.png" alt="image-20260222145428339"></p><p>Cursor中将Rules,Skills,Agents,MCP Servers等都统称为插件<a href="https://cursor.com/cn/docs/plugins">Plugins | Cursor Docs</a>,官方有插件市场可供安装，也可以本地配置。<a href="https://cursor.com/cn/marketplace">Cursor - Marketplace</a></p><h2 id="MCP-1"><a href="#MCP-1" class="headerlink" title="MCP"></a>MCP</h2><p><a href="https://registry.modelcontextprotocol.io/?all=1">Official MCP Registry</a></p><p><a href="https://github.com/modelcontextprotocol/servers/tree/main">modelcontextprotocol/servers: Model Context Protocol Servers</a></p><p><a href="https://mcp.so/">MCP Servers</a></p><p><a href="https://smithery.ai/">Smithery - Connect agents to MCPs in minutes</a></p><h1 id="Zed-OpenCode"><a href="#Zed-OpenCode" class="headerlink" title="Zed+OpenCode"></a>Zed+OpenCode</h1><p>OpenCode是开源的免费Agent,可以搭配一些模型提供商，而Zed主要是使用起来比较轻量，可以添加自定义LLM provider以及MCP。但目前说实话，各种功能和使用体验还是比不上Vscode。好处是集成了许多可用的Agent。<a href="https://www.runoob.com/ai-agent/opencode-coding-agent.html">OpenCode 入门教程 | 菜鸟教程</a></p><p><img data-src="https://files.seeusercontent.com/2026/02/22/saJ5/image-20260222161357220.png" alt="image-20260222161357220"></p><p>可以利用Claude Code或者OpenCode添加SKILLS<a href="https://ai.iamchen.cn/content/8">OpenCode 如何配置 MCP：完整指南 - AI开发小站</a></p><h2 id="安装OhMyOpenCode"><a href="#安装OhMyOpenCode" class="headerlink" title="安装OhMyOpenCode"></a>安装OhMyOpenCode</h2><p>oh-my-opencode 是一个为 OpenCode设计的强大插件/扩展层。它将单个 AI 代理升级为一个多智能体协作团队，提供开箱即用的高级功能。</p><p>推荐让 OpenCode 自动完成安装，复制以下提示并粘贴到你的 OpenCode 对话框：</p><figure class="highlight awk"><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">按照以下说明安装和配置 oh-my-opencode：</span><br><span class="line">https:<span class="regexp">//</span>raw.githubusercontent.com<span class="regexp">/code-yeongyu/</span>oh-my-opencode<span class="regexp">/refs/</span>heads<span class="regexp">/master/</span>docs<span class="regexp">/guide/i</span>nstallation.md</span><br></pre></td></tr></table></figure><p><img data-src="https://files.seeusercontent.com/2026/02/22/i6pC/image-20260222173037344.png" alt="image-20260222173037344"></p><h2 id="MCP-2"><a href="#MCP-2" class="headerlink" title="MCP"></a>MCP</h2><p><a href="https://opencode.ai/docs/mcp-servers/">MCP 服务器 | OpenCode</a></p><h2 id="SKILL-1"><a href="#SKILL-1" class="headerlink" title="SKILL"></a>SKILL</h2><p><a href="https://www.runoob.com/ai-agent/opencode-skills-intro.html">OpenCode skills 使用 | 菜鸟教程</a></p><p><img data-src="https://files.seeusercontent.com/2026/02/22/Ev7l/image-20260222172835008.png" alt="image-20260222172835008"></p><h1 id="国内翘楚Trae"><a href="#国内翘楚Trae" class="headerlink" title="国内翘楚Trae"></a>国内翘楚Trae</h1><p>国内的AI IDE/CLI主要是字节，阿里和腾讯，分别的代表性产品就是Trae,Qwen Code和CodeBuddy. 这里主要以Trae作说明，其更新比较快，使用体验目前也是最好。目前国内版还没有推出付费，但海外版有了SOLO付费模式。优点是不需要自己配置太多东西，开箱即用。</p><blockquote><p>SOLO 模式以 AI 为核心主导，全程自动规划与执行从需求理解、代码生成、测试到成果预览的全开发流程。在 SOLO 模式下， 我们只需通过自然语言描述、语音沟通或上传本地文件等灵活方式提交需求，AI 会快速自主拆解任务、高效推进执行，让开发过程变得极度简化、全程智能化。</p></blockquote><p>SOLO模式下还推出了SOLO Coder和SOLO Builder</p><h1 id="第三方资料"><a href="#第三方资料" class="headerlink" title="第三方资料"></a>第三方资料</h1><ol><li>API中转站<a href="https://github.com/whataicc/doc-whataicc-api">whataicc/doc-whataicc-api: 神马中转API教程文档<em>Claude中转教程_AI中转站配置使用教程</em>支持Claude、Midjourney、Suno、OpenAI等650+模型教程</a></li><li><a href="https://yibuapi.com/">一步API</a></li><li><a href="https://yunwu.ai/">API中转站 | 国内直连ChatGPT/Claude API服务 | 稳定可靠 - YUNWU API</a></li><li><a href="https://tsnapi.github.io/tsn-api/">简易API中转站 | 国内直连OpenAI/ChatGPT/GPT-4o/Claude API服务 | AI大模型中转平台</a></li><li><a href="https://juejin.cn/post/7494635102174609443?from=search-suggest">2025亲测！Cursor 使用 APIkey 配置 Claude , gpt-4o，deepseek等大模型 - 掘金</a></li><li><a href="https://docs.qinzhiai.com/">ClaudeHub - Claude Code 中转站 | 700+ AI 模型一个 API 搞定 | ClaudeHub - Claude Code 中转站</a></li><li><a href="https://www.runoob.com/ai-agent/opencode-skills-intro.html">OpenCode skills 使用 | 菜鸟教程</a></li><li><a href="https://www.runoob.com/ai-agent/ai-agent-tutorial.html">AI Agent(智能体) 教程 | 菜鸟教程</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/02/22/%E6%90%AD%E5%BB%BAAgent-MCP-SKILL%E8%BF%9B%E8%A1%8C%E6%88%90%E7%86%9FVibe-Coding/</id>
    <link href="https://www.sekyoro.top/2026/02/22/%E6%90%AD%E5%BB%BAAgent-MCP-SKILL%E8%BF%9B%E8%A1%8C%E6%88%90%E7%86%9FVibe-Coding/"/>
    <published>2026-02-22T04:36:38.000Z</published>
    <summary>
      <![CDATA[<p>Vibe Coding自提出以来，就有各大互联网公司、模型厂商以及开发者响应，提出许多基础优化编码。从模型的训练Function Calling能力和上下文长度提升,到Prompt技术，RAG增强，再到MCP统一协议获取信息，再到最新的SKILL。从Copilot,Cursor,Claude Code,OpenCode,Trae,国内外各大公司和开源产品都纷纷自己实现了相关协议或者提出了相关功能。这里就我比较常用的几个编码IDE进行实践，使得能够更加成熟方便地Vibe Coding.<br>]]>
    </summary>
    <title>搭建Agent+MCP+SKILL进行成熟Vibe Coding</title>
    <updated>2026-03-15T19:15:34.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="系统设计" scheme="https://www.sekyoro.top/tags/%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>搜集到了关于现代即时通讯(IM)系统中一些设计方案，作为分布式集群应用的集大成者，这里进行总结学习。这些资料基本来自52im.net。在此感谢站长和作者们。<br><span id="more"></span></p><h1 id="零基础入门"><a href="#零基础入门" class="headerlink" title="零基础入门"></a>零基础入门</h1><p>IM其实并不局限于聊天、社交这类“典型”应用中，实际上它已经广泛运用于我们身边形形色色的软件中。</p><p>聊天、直播、在线客服、物联网等所有需要实时互动、高实时性的场景等等，都需要应用到 IM 技术。</p><ul><li>1）微信、qq、钉钉等主流IM应用：这是IM技术的典型应用场景；</li><li>2）微博、知乎等社区应用：它们利用IM技术实现了用户私信等点对点聊天；</li><li>3）抖音、快手等直播/短视频应用：它们利用IM技术实现了与主播的实时互动；</li><li>4）米家等智能家居物联网应用：利用IM技术实现实时控制、远程监控等；</li><li>5）滴滴、Uber等共享家通类应用：利用IM技术实现位置共享；</li><li>6）在线教育类应用：利用IM技术实现在线白板。</li></ul><p><img data-src="https://files.seeusercontent.com/2026/02/16/M4sh/image-20260217005547159.png" alt="image-20260217005547159"></p><p><a href="https://learn.lianglianglee.com/专栏/即时消息技术剖析与实战">即时消息技术剖析与实战</a></p><h3 id="IM技术特点"><a href="#IM技术特点" class="headerlink" title="IM技术特点"></a>IM技术特点</h3><p> 1）实时性：</p><p>对于IM系统，“实时”二字是精髓，也是这项技术存在关键意义所在。它保证的是消息的实时触达。</p><p>举个例子：如果跟你的好友微信或qq聊天，我发的消息他不能即时收到，或者他发的信息你也不知道什么时候能收到，这基本上也就没法聊下去了</p><p>2）可靠性：</p><p>保证消息的不丢失和不重复，是IM系统的另一个关键技术特点。试想，当你在用qq或微信跟女朋友聊天，好不容易鼓起勇气向“她”表白，结果这消息要是丢包了，那肯定得卸载应用了，搞不好砸手机都有可能。当然，好话不说二遍，消息重复也同样恼人。</p><p>3）一致性：</p><p>对于单聊消息而言，保证同一个设备的时间顺序、不同设备的漫游同步，也是相当重要的一环。</p><p>IM系统中的消息交互，就到底就是人跟人在“说话”，前言不搭理后言、或者胡言乱语式的消息展现，那不是人疯了就是程序疯了，总之就是没法再聊下去了。</p><p>4）安全性：</p><p>保证数据传输安全、数据存储安全、消息内容安全，也是IM系统必不可少的特性。尤其在私聊场景下，如果不能做到安全性，聊天的体验跟被人偷窥的感觉是没有区别的。</p><h3 id="IM功能组成"><a href="#IM功能组成" class="headerlink" title="IM功能组成"></a>IM功能组成</h3><p>一个典型的IM功能组成，无非就是以下5样：</p><ul><li>1）联系人列表；</li><li>2）聊天界面；</li><li>3）消息发送通道；</li><li>4）消息接收通道；</li><li>5）消息存储；</li><li>6）消息未读数。</li></ul><p>联系人列表看似简单，实际上它是一系列IM系统的社交关系确立动作的结果体现。</p><p><strong>要想建立联系人列表，你可能需要实现以下逻辑：</strong></p><ul><li>1）怎么能找到想要聊天的人？（需要实现随机查找？精确查找？）</li><li>2）怎么决定要不要跟这个人聊？（需要实现对方的个人信息查看）</li><li>3）开始发出好友请求；</li><li>4）被请求的一方，还可以决定是“同意”还是“拒绝”（“同意”该怎么处理？“拒绝”又该怎么处理？）。</li></ul><p>聊天界面看似很平常，实际它就是IM系统客户端的核心功能所在，所有主要的IM功能都是通过它展现。</p><p><strong>它应该具备的能力有：</strong></p><ul><li>1）各种聊天功能按钮：语音留言、图片、文字、表情、文件、实时电话、实时视频等；</li><li>2）各种聊天消息显示：各种消息都有不同的UI显示元素和处理逻辑；</li><li>3）流畅的使用体验：大量不同类型的消息显示时，不能卡顿；</li><li>4）即时显示聊天消息：网络线程收到的消息，要马上在UI上显示出来；</li><li>5）历史消息的加载：上次聊过的内容也得显示出来吧。</li></ul><p>消息发送通道</p><ul><li>1）如何保证这条socket长连接时一直处于可用的状态？</li><li>2）当socket长连接不可用时，用户此时发送的消息该怎么处理？</li><li>3）怎么保证发送的消息不丢？</li><li>4）怎么保证发送的消息不重复？</li><li>5）怎么保证发送的消息乱序？</li><li>6）当对方不在线时，发送的消息去哪了？</li><li>7）发送的消息，能保证实时送到？</li></ul><p><strong>消息接收通道：</strong></p><p><strong>要实现一个可靠的消息接收通道，也并非易事：</strong></p><ul><li>1）如何保证socket长连接通道能随时处于良好的连接状态（随时接收对方write的消息）；</li><li>2）当socket长连接断开时，对方发送消息该怎么实现？</li><li>3）当socket恢复连接时，怎么恢复之前的聊天现场？</li><li>4）当我收到对方的消息时，对方怎么知道我已经收到了？</li><li>5）当重复收到对方的消息时，该怎么处理？</li><li><p>6）当收到的消息时序有错乱，该怎么处理？</p><p><strong><em>5）消息存储：\</em></strong></p></li></ul><p>消息存储这个功能好理解，聊天的消息如果存储，下次再聊的时候就不知道之前聊过什么，做不到这一点，这个IM系统的聊天体验好不起来。</p><p><strong>那么，哪些情况下需要进行消息存储呢：</strong></p><ul><li>1）对方不在线时：聊天消息应该存储（这叫离线消息存储）；</li><li>2）对方在线时：聊天消息也要存到本地存储（这叫消息缓存）；</li><li>3）对方在线或不在线时：聊天消息都要存到服务端（用于实现多设备的消息漫游和同步）。</li></ul><p><strong>但技术落到实处，要做的事情同样少不了：</strong></p><ul><li>1）离线消息该怎么多久？</li><li>2）图片、短视频、大文件这类的离线消息，多媒体文件该怎么存（有可能量会很大）？</li><li>3）当本地的消息积累太多时，怎么能保证本地存储的性能？</li><li>4）当应用更新、升级或异常时，怎么能保证本地存储的完整性（不被破坏）？</li><li>5）怎么能保证多设备消息能不丢、不重、不乱？</li></ul><p>消息未读数</p><p>1）未读数是客户端实现还是服务端实现？</p><p>2）会话未读和总未读怎么保持一致？</p><p>3）多终端情况下，怎么保证未读数的一致性（我在这台设备上读没读，那台设备怎么知道的？</p><h3 id="IM聊天的实时性"><a href="#IM聊天的实时性" class="headerlink" title="IM聊天的实时性"></a>IM聊天的实时性</h3><h4 id="短轮询技术"><a href="#短轮询技术" class="headerlink" title="短轮询技术"></a>短轮询技术</h4><p>在“短轮询”模式下，IM客户端定时轮询服务端，以便让用户知道是否有新的聊天消息存在。</p><p>这种模式下，服务端收到请求后，即刻查询是否存在新消息，有就返回给客户端，没有则返回空并立即关闭连接。相较于前面用户需要“手动”去刷新页面的方式，这种模式只是将用户的“手动”变为“自动”而已，技术本质并没有发生任何实质性改变。</p><p>短轮询这种模式，就好比旧时代一个等待重要邮件的人，他需要每天自已跑到邮局，主动去问是否有自己的信件，有就拿回家，如果没有，则第二天继续去问。一来一去，非常低效。</p><h4 id="长轮询技术"><a href="#长轮询技术" class="headerlink" title="长轮询技术"></a>长轮询技术</h4><p><strong>技术上来说，长轮询实现的IM相较于短轮询最大的改进在于：</strong>短轮询情况下，服务端不管有没有新消息，请求结束就会立即断开连接。而长轮询时，如果本次请求没有新消息发生，不会马上断开连接并返回，而是会将本次连接“挂起”一段时间，如果在这段“挂起”时间内有新的聊天消息出现，就能马上读取并立即返回给客户端，接着结束本次连接。一段时间后又会再次发起请求，如此周而复始。</p><p>长轮询这种模式，拿上节等待邮件的这个例子来说，就好比收信的人每天到邮局去问是否有信件，如果没有，他不马上回家，而是在邮局待上一段时间，如果这段时间过去了，还是没有，就先回家，接着第二天再来。</p><p><strong>长轮询的优点是：</strong></p><ul><li>1）相较于短连询，一定程度降低了服务端请求负载；</li><li>2）相较于短连询，实时性有提升，因为它是主动“等”消息。</li></ul><p><strong>长轮询的缺点是：</strong></p><ul><li>1）长论询模式下，连接“挂起”的这段时间内，服务端需要配合开启单独的消息查询线程，仍然存在无用功；</li><li>2）相较于短连询模式，在一次长轮询结束、下次轮询发起前的窗口期内，仍然存在“实时性”盲区。</li></ul><p>实际上，在Web端即时通讯技术里，长轮询有个专业的术语叫“Comet”，</p><p>在每次轮询结束和下次轮询开始的间隔期会形成“实时性盲区”。要理解纠结轮询技术的实时性缺陷，就得了解它们背后的技术——HTTP协议了。HTTP协议设计的目的，就是为了实现“请求—响应”这种模式的数据交互，也就是众所周之的“短连接”设计。而无论是短轮询还是长轮询，都跳不出HTTP的先天技术逻辑。</p><p>所以，归根到底，想要基于HTTP协议来实现IM，要达到真正的“实时性”，是相当勉强的。因为HTTP设计的目的，就是用“短连接”来简化传统TCP长连接通信带来的复杂性，而IM的实时性恰好要用到的又是TCP的长连接特性，所以这就是个悖论。</p><p>WebSocket是真正的全双式双向通信技术。</p><ul><li>1）轮询技术一问一答，在下一个请求发起之前，存在“实时性”盲区；</li><li><p>2）WebSocket一旦建立连接后，数据可以随时双向通信（即客户端可以随时向服务端发消息，服务端也可以随时通知客户端有新消息）。</p></li><li><p>1）真正的实时性：支持客户端与服务端真正的双向实时通信；</p></li><li>2）大幅降低负载：少了轮询技术中高频率无用的请求，可大大降低服务端QPS压力；</li><li>3）网络开销降低：一次连接，随时使用，再也不用轮询技术中每次发起HTTP请求（随之而来的是每次HTTP的大量冗余协议头信息等）。</li></ul><h3 id="IM聊天系统的可靠性"><a href="#IM聊天系统的可靠性" class="headerlink" title="IM聊天系统的可靠性"></a>IM聊天系统的可靠性</h3><p>M系统的消息“可靠性”，通常就是指聊天消息投递的可靠性（准确的说，这个“消息”是广义的，因为还存用户看不见的各种指令，为了通俗，统称“消息”）。</p><p><strong>从用户行为来讲，消息“可靠性”应该分为两种类型：</strong></p><ul><li>1）在线消息的可靠性：即发送消息时，接收方当前处于“在线”状态；</li><li>2）离线消息的可靠性：即发送消息时，接收方当前处于“离线”状态。</li></ul><p><strong>从具体的技术表现来讲，消息“可靠性”包含两层含义：</strong></p><ul><li><strong><em>1）</em></strong>消息不丢：这很直白，发出去的消息不能像进了黑洞一样；</li><li><strong><em>2）</em></strong>消息不重：这是丢消息的反面，消息重复了也不能容忍。</li></ul><p><strong>对于“消息不丢”这个特征来说，细化下来，它又包含两重含义：</strong></p><ul><li><strong><em>1）</em></strong>已明确被对方收到；</li><li><strong><em>2）</em></strong>已明确未被对方收到。</li></ul><p>典型的IM消息收发流程是 一条消息从客户端A发出后，需要先经过 IM 服务器来进行中转，然后再由 IM 服务器推送给客户端B，这种模式也是目前最常见的 IM 系统的消息分发架构。</p><p><img data-src="https://files.seeusercontent.com/2026/02/17/Xb1a/image-20260217153718305.png" alt="image-20260217153718305"></p><p>P2P模式在IM系统中用的很少。</p><p><strong>原因是以下两个很明显的弊端：</strong></p><ul><li><strong><em>1）</em></strong>P2P模式下，IM运营者很容易被用户架空</li><li><strong><em>2）</em></strong>P2P模式下，群聊这种业务形态，很难实现</li></ul><p><strong>TCP并不能保证在线消息的“可靠性”</strong></p><p>这次我们从客户端角度来理解，为什么使用了可靠传输协议TCP的情况下IM聊天消息仍然不可靠的问题。</p><p><strong>数据可靠抵达网络层之后，还需要一层层往上移交处理，可能的处理有：</strong></p><ul><li><strong><em>1）</em></strong>安全性校验；</li><li><strong><em>2）</em></strong>binary 解析；</li><li><strong><em>3）</em></strong>model 创建；</li><li><strong><em>4）</em></strong>写 db；</li><li><strong><em>5）</em></strong>存入 cache；</li><li><strong><em>6）</em></strong>UI 展示；</li><li><strong><em>7）</em></strong>以及一些边界问题：比如断网、用户突然退出登陆、磁盘已满、内存溢出、app奔溃、突然关机等等。</li></ul><p>例如，消息可靠抵达网络层之后，写 db 之前 IM APP 崩溃（不稀奇，是 App 都有崩溃的可能），虽然数据在网络层可靠抵达了，但没存进 db，下次用户打开 App 消息自然就丢失了，如果不在业务层再增加可靠性保障（比如：后面要提到的网络层面的消息重发保障），那么意味着这条消息对于接收端来说就永远丢失了，也就自然不存在“可靠性”了。</p><p><strong>为在线消息增加可靠性保障</strong></p><p>那么怎样在应用层增加可靠性保障呢？<strong>有一个现成的机制可供我们借鉴：</strong>TCP协议的超时、重传、确认机制。</p><p><strong>具体来说就是：</strong></p><ul><li><strong><em>1）</em></strong>在应用层构造一种ACK消息，当接收方正确处理完消息后，向发送方发送ACK；</li><li><strong><em>2）</em></strong>假如发送方在超时时间内没有收到ACK，则认为消息发送失败，需要进行重传或其他处理。</li></ul><p>可以把整个过程分为两个阶段。</p><p><strong><em>阶段1：clientA -&gt; server\</em></strong></p><ul><li>1-1：clientA向server发送消息(msg-Req)；</li><li>1-2：server收取消息，回复ACK(msg-Ack)给clientA；</li><li>1-3：一旦clientA收到ACK即可认为消息已成功投递，第一阶段结束。</li></ul><p>无论msg-A或ack-A丢失，<strong>clientA均无法在超时时间内收到ACK，此时可以提示用户发送失败，手动进行重发</strong>。</p><p><strong><em>阶段2：server -&gt; clientB\</em></strong></p><ul><li>2-1：server向clientB发送消息(Notify-Req)；</li><li>2-2：clientB收取消息，回复ACK(Notify-ACk)给server；</li><li>2-3：server收到ACK之后将该消息标记为已发送，第二阶段结束。</li></ul><p>无论msg-B或ack-B丢失，<strong>server均无法在超时时间内收到ACK，此时需要重发msg-B，直到clientB返回ACK为止</strong></p><h4 id="离线消息收发流程"><a href="#离线消息收发流程" class="headerlink" title="离线消息收发流程"></a>离线消息收发流程</h4><p><strong>离线消息收发流程也可划分为两个阶段：</strong></p><p><strong><em>阶段1：clientA -&gt; server\</em></strong></p><ul><li>1-1：clientA向server发送消息(msg-Req) ；</li><li>1-2：server发现clientB离线，将消息存入offline-DB。</li></ul><p><strong><em>阶段2：server -&gt; clientB\</em></strong></p><ul><li>2-1：clientB上线后向server拉取离线消息(pull-Req) ；</li><li>2-2：server从offline-DB检索相应的离线消息推送给clientB(pull-res)，并从offline-DB中删除。</li></ul><p><strong>显然：</strong>离线消息收发过程同样存在消息丢失的可能性。</p><p><strong>举例来说：</strong>假设pull-res没有成功送达clientB，而offline-DB中已删除，这部分离线消息就彻底丢失了。</p><p><img data-src="https://files.seeusercontent.com/2026/02/17/Qd9d/image-20260217160754364.png" alt="image-20260217160754364"></p><p><strong>与初始的离线消息收发流程相比，上图增加了1-3、2-4、2-5步骤：</strong></p><ul><li>1-3：server将消息存入offline-DB后，回复ACK(msg-Ack)给clientA，clientA收到ACK即可认为消息投递成功；</li><li>2-4：clientB收到推送的离线消息，回复ACK(res-Ack)给server；</li><li>2-5：server收到res-ACk后确定离线消息已被clientB成功收取，此时才能从offline-DB中删除。</li></ul><p><strong>当离线消息的量较大时：</strong>如果对每条消息都回复ACK，无疑会大大增加客户端与服务器的通信次数。这种情况我们通常使用批量ACK的方式，对多条消息仅回复一个ACK。还可以<strong>将所有的离线消息按会话进行分组，每组回复一个ACK，假如某个ACK丢失，则只需要重传该会话的所有离线消息。</strong></p><p>通过在应用层加入重传、确认机制后，我们确实是杜绝了消息丢失的可能性。</p><p><strong>但由于重试机制的存在，我们会遇到一个新的问题：</strong>那就是同一条消息可能被重复发送。</p><p><strong>举一个最简单的例子：</strong>假设client成功收到了server推送的消息，但其后续发送的ACK丢失了，那么server将会在超时后再次推送该消息，如果业务层不对重复消息进行处理，那么用户就会看到两条完全一样的消息。</p><p>消息去重的方式其实非常简单，一般是根据消息的唯一标志(id)进行过滤。</p><p><strong>具体过程在服务端和客户端可能有所不同：</strong></p><ul><li><strong><em>1）**</em></strong>客户端 ：可以通过构造一个map来维护已接收消息的id，当收到id重复的消息时直接丢弃；</li><li><strong><em>2）**</em></strong>服务端 ：收到消息时根据id去数据库查询，若库中已存在则不进行处理，但仍然需要向客户端回复Ack（因为这条消息很可能来自用户的手动重发）。</li></ul><h3 id="IM聊天系统的消息时序一致性"><a href="#IM聊天系统的消息时序一致性" class="headerlink" title="IM聊天系统的消息时序一致性"></a>IM聊天系统的消息时序一致性</h3><p><strong>所谓的一致性，在IM中通常指的是消息时序的一致性，那就是：</strong></p><ul><li>1）聊天消息的上下文连续性；</li><li>2）聊天消息的绝对时间序。</li></ul><p><strong>再具体一点，IM消息的一致性体现在：</strong></p><ul><li>1）单聊时：要保证发送方发出聊天消息的顺序与接收方看到的顺序一致；</li><li>2）群聊时：要保证所有群员看到的聊天消息，与发送者发出消息时的绝对时间序是一致的。</li></ul><p>技术难点：没有全局时钟</p><p>一个真正堪用的生产系统，显示不可能所有服务都跑在一台服务器上，分布式环境是肯定的。</p><p><strong>那么：</strong>在分布式环境下，客户端+服务端后台的各种后台服务，都各自分布在不同的机器上，机器之间都是使用的本地时钟，没有一个所谓的“全局时钟”（也没办法做到真正的全局时钟），那么所谓的消息时序也就没有真正意义上的时序基准点。所以消息时序问题显然不是“本地时间”可以完全决定的。</p><p>多发送方问题：服务端分布式的情况下，不能用“本地时间”来保证时序性，那么能否用接收方本地时间表示时序呢？遗憾的是，由于多个客户端的存在（比如群聊时），即使是一台服务器的本地时间，也无法表示“绝对时序”。</p><p>绝对时序上，APP1先发出msg1，APP2后发出msg2，都发往服务器web1，网络传输是不能保证msg1一定先于msg2到达的，所以即使以一台服务器web1的时间为准，也不能精准描述msg1与msg2的绝对时序。</p><p>多接收方问题，多发送方不能保证时序，假设只有一个发送方，能否用发送方的本地时间表示时序呢？遗憾的是，由于多个接收方的存在，无法用发送方的本地时间，表示“绝对时序”。绝对时序上，web1先发出msg1，后发出msg2，由于网络传输及多接收方的存在，无法保证msg1先被接收到先被处理，故也无法保证msg1与msg2的处理时序。</p><p>网络传输与多线程问题，既然多发送方与多接收方都难以保证绝对时序，那么假设只有单一的发送方与单一的接收方，能否保证消息的绝对时序一致性呢？</p><p>结论是悲观的，由于网络传输与多线程的存在，这仍然不行。</p><p>如上图所示，web1先发出msg1、后发出msg2，即使msg1先到达（网络传输其实还不能保证msg1先到达），由于多线程的存在，也不能保证msg1先被处理完。</p><h4 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h4><p>假设两人一对一聊天，发送方A依次发出了msg1、msg2、msg3三条消息给接收方B，这三条消息该怎么保证显示时序的一致性（发送与显示的顺序一致）？发送方A依次发出的msg1、msg2、msg3三条消息，到底服务端后，再由服务端中转发出时，这个顺序由于多线程的网络的问题，是有可能乱序的。</p><p>不过，实际上一对一聊天的两个人，并不需要全局消息时序的一致（因为聊天只在两人的同一会话在发生），只需要对于同一个发送方A，发给B的消息时序一致就行了。</p><p>常见优化方案，在A往B发出的消息中，加上发送方A本地的一个绝对时序（比如本机时间戳），来表示接收方B的展现时序。那么当接收方B收到消息后，即使极端情况下消息可能存在乱序到达，但因为这个乱序的时间差对于普通用户来说体感是很短的，在UI展现层按照消息中自带的绝对时序排个序后再显示，用户其实是没有太多感知的。</p><p>多对多群聊的消息一致性保证思路假设N个群友在一个IM群里聊天，应该怎样保证所有群员收到消息的显示时序一致性呢？不能像一对一聊天那样利用发送方的绝对时序来保证消息顺序，因为群聊发送方不单点，时间也不一致。可以利用服务器的单点做序列化。</p><ul><li>1）sender1发出msg1，sender2发出msg2；</li><li>2）msg1和msg2经过接入集群，服务集群；</li><li>3）service层到底层拿一个唯一seq，来确定接收方展示时序；</li><li>4）service拿到msg2的seq是20，msg1的seq是30；</li><li>5）通过投递服务将消息发给多个群友，群友即使接收到msg1和msg2的时间不同，但可以统一按照seq来展现。</li></ul><p><strong>这个方法：</strong></p><ul><li>1）优点是：能实现所有群友的消息展示时序相同；</li><li>2）缺点是：这个生成全局递增序列号的服务很容易成为系统瓶颈</li></ul><p>群消息其实也不用保证全局消息序列有序，而只要保证一个群内的消息有序即可，这样的话，“消息id序列化”就成了一个很好的思路。</p><p><img data-src="https://files.seeusercontent.com/2026/02/17/K6tr/image-20260217172530135.png" alt="image-20260217172530135"></p><p>群消息其实不用保证全局消息序列有序，而只要保证一个群内的消息有序即可，这样的话，“消息id序列化”就成了一个很好的思路。service层不再需要去一个统一的后端拿全局seq（序列号），而是在service连接池层面做细小的改造，保证一个群的消息落在同一个service上，这个service就可以用本地seq来序列化同一个群的所有消息，保证所有群友看到消息的时序是相同的。这样就需要全局消息ID生成方案，对于IM系统来说，绝对意义上的时序很难保证，但通过服务端生成的单调递增消息ID的方式，利用递增ID来保证时序性，也是一个很可性的方案。</p><h2 id="IM聊天系统的端到端加密"><a href="#IM聊天系统的端到端加密" class="headerlink" title="IM聊天系统的端到端加密"></a>IM聊天系统的端到端加密</h2><p>一般的数据加密可以在通信的3个层次来实现：链路加密、节点加密和端到端加密。</p><p><strong>链路加密</strong></p><p>对于在两个网络节点间的某一次通信链路，链路加密能为网上传输的数据提供安全保证。对于链路加密(又称在线加密)，所有消息在被传输之前进行加密，在每一个节点对接收到的消息进行解密，然后先使用下一个链路的密钥对消息进行加密，再进行传输。</p><p>在到达目的地之前，<strong>一条消息可能要经过许多通信链路的传输。由于在每一个中间传输节点消息均被解密后重新进行加密</strong>，因此，包括路由信息在内的链路上的所有数据均以密文形式出现，这样，链路加密就掩盖了被传输消息的源点与终点。</p><p><strong>节点加密</strong></p><p>尽管节点加密能给网络数据提供较高的安全性，但它在操作方式上与链路加密是类似的：两者均在通信链路上为传输的消息提供安全性，都在中间节点先对消息进行解密，然后进行加密。因为要对所有传输的数据进行加密，所以加密过程对用户是透明的。然而，与链路加密不同，<strong>节点加密不允许消息在网络节点以明文形式存在，它先把收到的消息进行解密，然后采用另一个不同的密钥进行加密，这一过程是在节点上的一个安全模块中进行</strong>。</p><p>节点加密要求报头和路由信息以明文形式传输，以便中间节点能得到如何处理消息的信息，因此这种方法对于防止攻击者分析通信业务是脆弱的。</p><p><strong>端到端加密</strong></p><p>端到端加密允许数据在从源点到终点的传输过程中始终以密文形式存在。采用端到端加密(又称脱线加密或包加密)，<strong>消息在被传输时到达终点之前不进行解密，因为消息在整个传輸过程中均受到保护，所以即使有节点被损坏也不会使消息泄露。</strong>端到端加密系统的价格便宜些，并且与链路加密和节点加密相比更可靠，更容易设计、实现和维护。端到端加密还避免了其它加密系统所固有的同步问题，因为每个报文包均是独立被加密的，所以一个报文包所发生的传输错误不会影响后续的报文包。端到端加密系统通常不允许对消息的目的地址进行加密，这是因为每一个消息所经过的节点都要用此地址来确定如何传输消息。由于这种加密方法不能掩盖被传输消息的源点与终点，因此它对于防止攻击者分析通信业务是脆弱的。</p><p>在IM系统中，当用户A发送消息给用户B时，IM系统会生成一对公钥和私钥，并将公钥发送给用户B。用户A使用用户B的公钥对消息进行加密，然后将加密后的消息发送给用户B。在用户B接收到消息后，使用自己的私钥对消息进行解密，从而获取明文内容。由于私钥只有用户B拥有，因此除了用户B之外，任何人都无法解密消息。</p><p><strong>通讯效率低：</strong>由于端对端加密需要对通讯数据进行加密和解密，因此可能会导致通信效率较低。</p><p><strong>需双向支持：</strong>端对端加密需要发送方和接收方都需要支持该技术，否则就将无法实现端对端加密通信。</p><p><strong>安全性问题：</strong>虽然端对端加密可以防止中间人攻击，但如果黑客能够获得了私钥或公钥，那么他们也能够轻易地获取到通信数据。</p><h1 id="简易架构设计"><a href="#简易架构设计" class="headerlink" title="简易架构设计"></a>简易架构设计</h1><p>IM系统设计设计方方面面，这里不作细究。从功能来说包括账号，关系链，在线状态显示，消息交互等等。</p><p>一种简单架构如下</p><p><img data-src="http://www.52im.net/data/attachment/forum/201605/14/162222squ23uqu4nq308nh.png" alt="浅谈IM系统的架构设计_QQ20160514-4.png" style="zoom:67%;" /></p><p>对于上线服务由于建立的是<strong>TCP长连接</strong>，对于单台服务器往往由于硬件资源、系统资源、网络资源的限制无法做到海量用户的同时 在线，所以设计为根据服务器负载支持多服务器上线，同时由于多服务器上线造成了对整个系统交互（不同的客户端的交互，协作部门应用服务和客户的交互）的分 割，引入消息转发服务器作为粘合点。另外对于多服务器上线造成的统一账户信息（在线状态，消息）数据的分割，引入统一的数据层（内存存储 层：session、状态信息存储、消息队列存储；数据库：账号信息存储）做到业务和数据的分离，也就做到了支持分布式部署。</p><p>一种现代架构如下图</p><p><img data-src="http://www.52im.net/data/attachment/forum/202305/28/212610ljei4jot6zu5wtwg.png" alt="跟着源码学IM(十一)：一套基于Netty的分布式高可用IM详细设计与实现(有源码)_1.png" style="zoom:50%;" /></p><p>高并发上又可以从水平扩展，线程模型，多层缓存，长连接等优化。</p><p>高可用上包括心跳，稳定性以及Redis宕机高可用等。</p><p>从流程上来看主要有单聊和群聊流程。</p><h3 id="单聊流程"><a href="#单聊流程" class="headerlink" title="单聊流程"></a>单聊流程</h3><p>假设是用户A发消息给用户B ，以下是完整的业务流程。</p><p><strong><em>1）</em></strong>A打包数据发送给服务端，服务端接收消息后，根据接收消息的sequence_id来进行客户端发送消息的去重，并且生成递增的消息ID，将发送的信息和ID打包一块入库，入库成功后返回ACK，ACK包带上服务端生成的消息ID。</p><p><strong><em>2）</em></strong>服务端检测接收用户B是否在线，在线直接推送给用户B。</p><p><strong><em>3）</em></strong>如果没有本地消息ID则存入，并且返回接入层ACK信息；如果有则拿本地sequence_id和推送过来的sequence_id大小对比，并且去重，进行展现时序进行排序展示，并且记录最新一条消息ID。最后返回接入层ack。</p><p><strong><em>4）</em></strong>服务端接收ACK后，将消息标为已送达。</p><p><strong><em>5）</em></strong>如果用户B不在线,首先将消息存入库中，然后直接通过手机通知来告知客户新消息到来。</p><p><strong><em>6）</em></strong>用户B上线后，拿本地最新的消息ID，去服务端拉取所有好友发送给B的消息，考虑到一次拉取所有消息数据量大，通过channel通道来进行分页拉取，将上一次拉取消息的最大的ID，作为请求参数，来请求最新一页的比ID大的数据。</p><h3 id="群聊流程"><a href="#群聊流程" class="headerlink" title="群聊流程"></a>群聊流程</h3><p>假设是用户A发消息给群G ，以下是完整的业务流程。</p><p><strong><em>1）</em></strong>登录，TCP连接，token校验，名词检查，sequence_id去重，生成递增的消息ID，群消息入库成功返回发送方ACK。</p><p><strong><em>2）</em></strong>查询群G所有的成员，然后去redis中央存储中找在线状态。离线和在线成员分不同的方式处理。</p><p><strong><em>3）</em></strong>在线成员：并行发送拉取通知，等待在线成员过来拉取，发送拉取通知包如丢失会有兜底机制。</p><p><strong><em>4）</em></strong>在线成员过来拉取，会带上这个群标识和上一次拉取群的最小消息ID，服务端会找比这个消息ID大的所有的数据返回给客户端，等待客户端ACK。一段时间没ack继续推送。如果重试几次后没有回ack，那么关闭连接和清除ack等待队列消息。</p><p><strong><em>5）</em></strong>客户端会更新本地的最新的消息ID，然后进行ack回包。服务端收到ack后会更新群成员的最新的消息ID。</p><p><strong><em>6）</em></strong>离线成员：发送手机通知栏通知。离线成员上线后，拿本地最新的消息ID，去服务端拉取群G发送给A的消息，通过channel通道来进行分页拉取，每一次请求，会将上一次拉取消息的最大的ID，作为请求参数来拉取消息，这里相当于第二次拉取请求包是作为第一次拉取的ack包。</p><p><strong><em>7）</em></strong>分页的情况下，客户端在收到上一页请求的的数据后更新本地的最新的消息ID后，再请求下一页并且带上消息ID。上一页请求的的数据可以当作为ack来返回服务端，避免网络多次交互。服务端收到ack后会更新群成员的最新的消息ID。</p><h3 id="客户端设计"><a href="#客户端设计" class="headerlink" title="客户端设计"></a>客户端设计</h3><p><strong>客户端的设计主要从以下几点出发：</strong></p><ul><li><strong><em>1）</em></strong>client每个设备会<strong>在本地存每一个会话，保留有最新一条消息的顺序 ID；</strong></li><li><strong><em>2）</em></strong>为了避免client宕机，也就是退出应用，保存在内存的消息ID丢失，会存到本地的文件中；</li><li><strong><em>3）</em></strong>client需要在<strong>本地维护一个等待ack队列，并配合timer超时机制，来记录哪些消息没有收到ack</strong>：N，以定时重发；</li><li><strong><em>4）</em></strong>客户端本地生成一个递增序列号发送给服务器，用作保证发送顺序性。该序列号还用作ack队列收消息时候的移除</li></ul><p><strong><em>1）方案一：</em></strong></p><p><img data-src="http://www.52im.net/data/attachment/forum/202305/28/213004y5x9y5f1dvjo9xoj.png" alt="跟着源码学IM(十一)：一套基于Netty的分布式高可用IM详细设计与实现(有源码)_2.png"></p><p><strong>设计思路：</strong></p><ul><li><strong><em>1）</em></strong>数据传输中的大小尽量小用int，不用bigint，节省传输大小；</li><li><strong><em>2）</em></strong>只保证递增即可,在<strong>用户重新登录或者重连后可以进行日期重置</strong>，只保证单次；</li><li><strong><em>3）</em></strong>客户端发号器不需要像类似服务器端发号器那样集群部署，不需要考虑集群同步问题。</li></ul><p><strong>注：</strong>上述生成器可以用18年[(2^29-1)/3600/24/365]左右，一秒内最多产生4个消息。</p><p><strong>优点：</strong>可以在断线重连和重装APP的情况下，18年之内是有序的。<br><strong>缺点：</strong>每秒只能发4个消息，限制太大，对于群发场景不合适。<br><strong>改进：</strong>使用long进行传输，年限扩展很久并且有序。</p><p><strong><em>2）方案二：</em></strong></p><p><strong>设计思路：</strong></p><ul><li><strong><em>1）</em></strong>每次重新建立链接后进行重置，将sequence_id（int表示）从0开始进行严格递增；</li><li><strong><em>2）</em></strong>客户端发送消息会带上唯一的递增sequence_id，同一条消息重复投递的sequence_id是一样的；</li><li><strong><em>3）</em></strong>后端存储每个用户的sequence_id，当sequence_id归0，用户的epoch年代加1存储入库，单聊场景下转发给接收者时候，接收者按照sequence_id和epoch来进行排序。</li></ul><p><strong>优点：</strong>可以在断线重连和重装APP的情况下，接收者可以按照发送者发送时序来显示，并且对发送消息的速率没限制。</p><h3 id="转发接入层"><a href="#转发接入层" class="headerlink" title="转发接入层"></a>转发接入层</h3><p>IM接入层的高可用、负载均衡、扩展性全部在这里面做。客户端通过LSB，来获取gate IP地址，通过IP直连。</p><p><strong>这样做的目的是：</strong></p><ul><li><strong><em>1）</em></strong>灵活的负载均衡策略 可根据最少连接数来分配IP；</li><li><strong><em>2）</em></strong>做灰度策略来分配IP；</li><li><strong><em>3）</em></strong>AppId业务隔离策略 不同业务连接不同的gate，防止相互影响；</li><li><strong><em>4）</em></strong>单聊和群聊的im接入层通道分开。</li></ul><p><strong>上述设计存在一个问题：</strong>就是<strong>当某个实例重启后，该实例的连接断开后，客户端会发起重连，重连就大概率转移其他实例上，导致最近启动的实例连接数较少</strong>，最早启动的实例连接数较多。</p><p><strong>解决方法：</strong></p><ul><li><strong><em>1）</em></strong>客户端会发起重连，跟服务器申请重连的新的服务器IP，<strong>系统提供合适的算法来平摊gate层的压力，防止雪崩效应</strong>；</li><li><strong><em>2）</em></strong>gate层定时上报本机的元数据信息以及连接数信息，提供给LSB中心，LSB根据最少连接数负载均衡实现，来计算一个节点供连接。</li></ul><h3 id="消息投递"><a href="#消息投递" class="headerlink" title="消息投递"></a>消息投递</h3><p><strong>一个正常的消息流转需要如下图所示的流程：</strong></p><p><img data-src="http://www.52im.net/data/attachment/forum/202305/28/221421bjitiyz0tibj9oyf.png" alt="跟着源码学IM(十一)：一套基于Netty的分布式高可用IM详细设计与实现(有源码)_5.png" style="zoom:50%;" /></p><p><strong>如上图所示：</strong></p><ul><li><strong><em>1）</em></strong>客户端A发送请求包R；</li><li><strong><em>2）</em></strong>server将消息存储到DB；</li><li><strong><em>3）</em></strong>存储成功后返回确认ack；</li><li><strong><em>4）</em></strong>server push消息给客户端B；</li><li><strong><em>5）</em></strong>客户端B收到消息后返回确认ack；</li><li><strong><em>6）</em></strong>server收到ack后更新消息的状态或者删除消息。</li></ul><ol><li>接入层预处理（Gateway/Connector）</li></ol><p>消息首先到达网关节点，执行以下校验：</p><ul><li><strong>安全鉴权</strong>：校验 A 的 Token 是否合法、连接是否有效。</li><li><strong>幂等去重</strong>：利用消息包携带的 <code>ClientMsgId</code>，在 Redis 中检查是否为重发请求，防止因网络抖动导致重复入库。</li><li><strong>协议解析</strong>：将前端的协议（如 WebSocket/Protobuf）转换为服务端内部的通用模型。</li></ul><ol><li>消息原子性处理与保序（Logic Server）</li></ol><p>这是流程中最关键的“逻辑中枢”：</p><ul><li><strong>分配序列号（SeqID）</strong>：服务端根据 <code>SessionID</code> 去 Redis 中请求一个<strong>单调递增</strong>的序号。这个 <code>SeqID</code> 是保证 B 侧消息不乱序的唯一标准。</li><li><strong>写入持久化库（存储库）</strong>：将消息内容、发送者、接收者、<code>SeqID</code> 等完整写入分布式数据库（如 HBase 或 MySQL）。</li><li><strong>写入同步库（Sync Store）</strong>：同时写入 Redis 的 ZSet 结构（Timeline 模型），为 B 上线后的增量同步做准备。</li></ul><ol><li>分布式路由寻址</li></ol><p>消息存好后，需要找到 B 到底在哪台机器上：</p><ul><li><strong>查询 Redis 路由表</strong>：根据接收方 B 的 <code>UserId</code>，查询其当前的在线状态及对应的 <code>NodeID</code>（网关节点 ID）。</li><li><strong>判定投递路径</strong>：<ul><li><strong>在线</strong>：获取 B 所在的网关 IP，进入实时转发逻辑。</li><li><strong>离线</strong>：直接结束实时流程，记录未读数，等待 B 上线后触发离线拉取，或者触发第三方推送（如厂商推送 APNs/华为推送）。</li></ul></li></ul><ol><li>跨机转发与实时推送</li></ol><p>通过消息队列（MQ）进行服务器间的“转运”：</p><ul><li><strong>MQ 转发</strong>：服务器 A 将消息投递到特定的 MQ Topic（通常以 B 所在的 <code>NodeID</code> 命名）。</li><li><strong>服务器 B 消费</strong>：服务器 B 监听到消息后，根据内部的 <code>ChannelID</code>（长连接通道）找到 B 的物理连接。</li><li><strong>下发推送</strong>：通过 WebSocket/TCP 将带有 <code>SeqID</code> 的消息包推给客户端 B。</li></ul><ol><li>确认与反馈（ACK 闭环）</li></ol><p>为了确保“不丢失”，必须完成闭环：</p><ul><li><strong>服务端响应 A</strong>：一旦消息入库成功，服务端立即返回一个 <code>SERVER_ACK</code> 给 A，告知消息已妥投（此时 A 端的“发送中”菊花图标消失）。</li><li><strong>等待 B 的回执</strong>：服务端将该消息放入“待确认队列”。直到收到客户端 B 发回的 <code>CLIENT_ACK</code>，才将该消息从重传列表中删除。</li></ul><h1 id="聊天消息的同步和存储方案"><a href="#聊天消息的同步和存储方案" class="headerlink" title="聊天消息的同步和存储方案"></a>聊天消息的同步和存储方案</h1><p><strong>IM系统中最核心的部分是消息系统，消息系统中最核心的功能是消息的同步和存储：</strong></p><blockquote><p><strong>1）消息的同步：</strong>将消息完整的、快速的从发送方传递到接收方，就是消息的同步。<a href="https://zhida.zhihu.com/search?content_id=4739490&amp;content_type=Article&amp;match_order=1&amp;q=消息同步&amp;zhida_source=entity">消息同步</a>系统最重要的衡量指标就是消息传递的实时性、完整性以及能支撑的消息规模。从功能上来说，一般至少要支持在线和离线推送，高级的IM系统还支持『多端同步』；<br><strong>2）消息的存储：</strong>消息存储即消息的持久化保存，这里不是指消息在客户端本地的保存，而是指云端的保存，功能上对应的就是『消息漫游』。『消息漫游』的好处是可以实现账号在任意端登陆查看所有历史消息，这也是高级IM系统特有的功能之一。</p></blockquote><p>一种传统架构设计是消息先同步后存储</p><p><img data-src="https://files.seeusercontent.com/2026/02/13/V5mx/image-20260213165208138.png" alt="image-20260213165208138"></p><p>对于在线的用户，消息会直接实时同步到在线的接收方，消息同步成功后，并不会进行持久化。而对于离线的用户或者消息无法实时同步成功时，消息会持久化到离线库，当接收方重新连接后，会从离线库拉取所有未读消息。当离线库中的消息成功同步到接收方后，消息会从离线库中删除。传统的消息系统，服务端的主要工作是维护发送方和接收方的连接状态，并提供在线消息同步和离线消息缓存的能力，保证消息一定能够从发送方传递到接收方。服务端不会对消息进行持久化，所以也无法支持消息漫游。</p><p><img data-src="https://files.seeusercontent.com/2026/02/13/9meM/image-20260213210649588.png" alt="image-20260213210649588"></p><p>现代架构中最核心的就是两个消息库『消息同步库』和『消息存储库』，是消息同步和存储最核心的基础。</p><h3 id="Timeline逻辑模型"><a href="#Timeline逻辑模型" class="headerlink" title="Timeline逻辑模型"></a>Timeline逻辑模型</h3><p><strong>Timeline可以简单理解为是一个消息队列，但这个消息队列有如下特性：</strong></p><p>每个消息拥有一个顺序ID（SeqId），在队列后面的消息的SeqId一定比前面的消息的SeqId大，也就是保证SeqId一定是增长的，但是不要求严格递增；新的消息永远在尾部添加，保证新的消息的SeqId永远比已经存在队列中的消息都大；可根据SeqId随机定位到具体的某条消息进行读取，也可以任意读取某个给定范围内的所有消息。</p><p>基于Timeline逻辑模型，可以分析消息存储模型和消息同步模型。</p><p>消息的同步可以拿Timeline来很简单的实现。图中的例子中，消息发送方是A，消息接收方是B，同时B存在多个接收端，分别是B1、B2和B3。A向B发送消息，消息需要同步到B的多个端，待同步的消息通过一个Timeline来进行交换。A向B发送的所有消息，都会保存在这个Timeline中，B的每个接收端都是独立的从这个Timeline中拉取消息。<strong>每个接收端同步完毕后，都会在本地记录下最新同步到的消息的SeqId，即最新的一个位点，作为下次消息同步的起始位点</strong>。<strong>服务端不会保存各个端的同步状态</strong>，各个端均可以在任意时间从任意点开始拉取消息。</p><p>消息存储/漫游也是基于Timeline，和消息同步唯一的区别是，消息存储要求服务端能够对Timeline内的所有数据进行持久化。</p><h4 id="消息存储模型"><a href="#消息存储模型" class="headerlink" title="消息存储模型"></a>消息存储模型</h4><p><img data-src="https://files.seeusercontent.com/2026/02/14/p2bL/image-20260214100630240.png" alt="image-20260214100630240"></p><p>消息存储要求每个会话都对应一个独立的Timeline。如图例子所示，A与B/C/D/E/F均发生了会话，每个会话对应一个独立的Timeline，每个Timeline内存有这个会话中的所有消息，服务端会对每个Timeline进行持久化。服务端能够对所有会话Timeline中的全量消息进行持久化，也就拥有了消息漫游的能力。</p><h4 id="消息同步模型"><a href="#消息同步模型" class="headerlink" title="消息同步模型"></a>消息同步模型</h4><p>消息的同步一般有读扩散和写扩散两种不同的方式，分别对应不同的Timeline物理模型。</p><p><strong>读扩散</strong></p><p>也叫“发件箱模型”。A 发一条消息，系统只存一份到<strong>群的 Timeline</strong> 里。B、C、D 上线时，去群的表里拉取属于自己的消息。</p><p>读扩散的消息同步模式下，每个会话中产生的新的消息，<strong>只需要写一次到其用于存储的Timeline中，接收端从这个Timeline中拉取新的消息。</strong><br>优点是消息只需要写一次，相比写扩散的模式，能够大大降低消息写入次数，特别是在群消息这种场景下。但其缺点也比较明显，接收端去同步消息的逻辑会相对复杂和低效。接收端需要对每个会话都拉取一次才能获取全部消息，读被大大的放大，并且会产生很多无效的读，因为并不是每个会话都会有新消息产生。</p><p><strong>优点</strong>：</p><ul><li><strong>写极快</strong>：不管群里有多少人，只写一次数据库。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>读很重</strong>：如果用户加了 500 个群，每次刷新要拉取 500 个群的数据再做聚合。</li><li><strong>未读数复杂</strong>：需要记录每个人在每个群里读到了哪一条（LastReadMsgId）。</li></ul><p><strong>写扩散</strong></p><p>写扩散的消息同步模式，也叫“收件箱模型”。，需要有一个额外的Timeline来专门用于消息同步，通常是<strong>每个接收端都会拥有一个独立的同步Timeline，用于存放需要向这个接收端同步的所有消息。</strong><br>每个会话中的消息，会产生多次写，除了写入用于消息存储的会话Timeline，还需要写入需要同步到的接收端的同步Timeline。在个人与个人的会话中，消息会被额外写两次，除了写入这个会话的存储Timeline，还需要写入参与这个会话的两个接收者的同步Timeline。而在群这个场景下，写入会被更加的放大，如果这个群拥有N个参与者，那每条消息都需要额外的写N次。<br>写扩散同步模式的优点是，在接收端消息同步逻辑会非常简单，只需要从其同步Timeline中读取一次即可，大大降低了消息同步所需的读的压力。其缺点就是消息写入会被放大，特别是针对群这种场景。</p><p><strong>优点</strong>：</p><ul><li><strong>读极快</strong>：用户看消息时，只需要读取自己的收件箱，不需要去别人的表里翻找。</li><li><strong>逻辑简单</strong>：未读数、排序都在自己表里。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>写放大</strong>：一个 2000 人的群，发一条消息要写 2000 次数据库。如果是万人数的大群，瞬间的写入压力会击垮数据库。</li></ul><p>在IM这种应用场景下，通常会选择写扩散这种消息同步模式。</p><p>IM场景下，一条消息只会产生一次，但是会被读取多次，是典型的读多写少的场景，消息的读写比例大概是10:1。若使用读扩散同步模式，整个系统的读写比例会被放大到100:1。</p><p>一个优化的好的系统，必须从设计上去平衡这种读写压力，避免读或写任意一维触碰到天花板。所以IM系统这类场景下，通常会应用写扩散这种同步模式，来平衡读和写，将100:1的读写比例平衡到30:30。</p><p>当然写扩散这种同步模式，还需要处理一些极端场景，例如万人大群。针对这种极端写扩散的场景，会退化到使用读扩散。一个简单的IM系统，通常会在产品层面限制这种大群的存在，而对于一个高级的IM系统，会采用读写扩散混合的同步模式，来满足这类产品的需求.</p><p>现代 IM（如微信、钉钉）不会只用一种，而是<strong>动态切换</strong>：</p><p>A. 单聊与常规小群（写扩散为主）</p><ul><li><strong>逻辑</strong>：绝大多数人的联系人和群组规模都在 500 人以内。</li><li><strong>做法</strong>：采用<strong>写扩散</strong>。直接把消息同步到每个人的同步库（Sync Store）。</li><li><strong>结果</strong>：保证了普通用户查看聊天列表时的极致流畅度。</li></ul><p>B. 超大群/直播间（读扩散为主）</p><ul><li><strong>逻辑</strong>：比如一个 1 万人的企业全员群，或者几万人的直播间。</li><li><strong>做法</strong>：采用<strong>读扩散</strong>。消息只存一份在“群 Timeline”。</li><li><strong>同步策略</strong>：当用户点进这个特定的大群时，客户端才去请求该群的增量数据。</li></ul><h4 id="消息库设计"><a href="#消息库设计" class="headerlink" title="消息库设计"></a>消息库设计</h4><p>消息同步库</p><p>消息同步库用于存储所有用于消息同步的Timeline，每个Timeline对应一个接收端，主要用作写扩散模式的消息同步。<br>这个库不需要永久保留所有需要同步的消息，<strong>因为消息在同步到所有端后其生命周期就可以结束，就可以被回收</strong>。但是如前面所介绍的，一个实现简单的多端同步消息系统，<strong>在服务端不会保存有所有端的同步状态，而是依赖端自己主动来做同步。</strong><br>所以服务端不知道消息何时可以回收，通常的做法是<strong>为这个库里的消息设定一个固定的生命周期，例如一周或者一个月，生命周期结束可被淘汰。</strong></p><p>消息存储库</p><p>消息存储库用于存储所有会话的Timeline，每个Timeline包含了一个会话中的所有消息。这个库主要用于消息漫游时拉取某个会话的所有历史消息，也用于读扩散模式的消息同步。</p><p><strong>数据库选型</strong></p><div class="table-container"><table><thead><tr><th>消息同步库</th><th>消息存储库</th></tr></thead><tbody><tr><td>高并发写，十万级TPS</td><td>高并发写，少量读，万级TPS</td></tr><tr><td>高并发范围读，十万级TPS</td><td>少范围读，千级TPS</td></tr><tr><td>保存一段时间的同步消息，TB级别。保留千万级的Timeline规模</td><td>保存全量信息，百TB级别。保留亿级Timeline规模</td></tr></tbody></table></div><p>1）表结构设计能够满足Timeline模型的功能要求：不要求关系模型，能够实现队列模型，并能够支持生成自增的SeqId；<br>2）能够支持高并发写和范围读，规模在十万级TPS；<br>3）能够保存海量数据，百TB级；<br>4）能够为数据定义生命周期。</p><h2 id="消息送达保证机制实现"><a href="#消息送达保证机制实现" class="headerlink" title="消息送达保证机制实现"></a>消息送达保证机制实现</h2><p>IM的客户端与服务器通过发送报文（也就是请求包）来完成消息的传递。</p><p><strong>报文分为三种：</strong></p><ul><li>请求报文（request，后简称为为R）； 客户端主动发送给服务器的报文</li><li>应答报文（acknowledge，后简称为A）；务器被动应答客户端的报文，一个A一定对应一个R</li><li>通知报文（notify，后简称为N）服务器主动发送给客户端的报文</li></ul><p>用户A给用户B发送一个“你好”，流程如下：</p><p><img data-src="http://www.52im.net/data/attachment/forum/201611/13/224301vyyfwjqylf0jylq6.png" alt="IM消息送达保证机制实现(一)：保证在线实时消息的可靠投递_2.png"></p><ul><li>client-A向im-server发送一个消息请求包，即msg:R</li><li>im-server在成功处理后，回复client-A一个消息响应包，即msg:A</li><li>如果此时client-B在线，则im-server主动向client-B发送一个消息通知包，即msg:N（当然，如果client-B不在线，则消息会存储离线）</li></ul><p>发送方client-A收到msg:A后，只能说明im-server成功接收到了消息，并不能说明client-B接收到了消息。在若干场景下，可能出现msg:N包丢失，且发送方client-A完全不知道，例如：</p><ul><li>服务器崩溃，msg:N包未发出</li><li>网络抖动，msg:N包被网络设备丢弃</li><li>client-B崩溃，msg:N包未接收</li></ul><p>接收方client-B是否有收到msg:N，发送方client-A完全不可控</p><h4 id="应用层确认-IM消息可靠投递"><a href="#应用层确认-IM消息可靠投递" class="headerlink" title="应用层确认+IM消息可靠投递"></a>应用层确认+IM消息可靠投递</h4><blockquote><p>TCP 协议本身就是可靠的（有确认、重传、有序），那 IM 系统的业务层为什么还要大费周章地搞应用层 ACK 和 SeqID 呢？<strong>TCP 的可靠性只存在于“传输层”，而 IM 的可靠性必须建立在“业务层”</strong></p><p>TCP 只能保证“发到网卡”,不能保证“存入数据库”,“消息有序”的维度不同,网络链路复杂:TCP 连接不断不代表业务可用</p></blockquote><p>UDP是一种不可靠的传输层协议，TCP是一种可靠的传输层协议，TCP是如何做到可靠的？答案是：超时、重传、确认。（实际上IM中，数据通讯层无论用的是UDP还是TCP协议，都同样需要消息送达保证（即QoS）机制，原因在于IM的通信是A端-Server-B端的3方通信，而非传统C/S或B/S这种2方通信）。</p><p>要想实现应用层的消息可靠投递，必须加入应用层的确认机制，即：要想让发送方client-A确保接收方client-B收到了消息，必须让接收方client-B给一个消息的确认，这个应用层的确认的流程，与消息的发送流程类似：</p><ul><li>client-B向im-server发送一个ack请求包，即ack:R</li><li>im-server在成功处理后，回复client-B一个ack响应包，即ack:A</li><li>则im-server主动向client-A发送一个ack通知包，即ack:N</li></ul><p>至此，发送“你好”的client-A，在收到了ack:N报文后，才能确认client-B真正接收到了“你好”。</p><p><strong>一条消息的发送，分别包含（上）（下）两个半场，即msg的R/A/N三个报文，ack的R/A/N三个报文</strong>。一个应用层即时通讯消息的可靠投递，共涉及6个报文，这就是im系统中消息投递的最核心技术（如果某个im系统不包含这6个报文，不要谈什么消息的可靠性）。</p><p><strong>期望六个报文完成消息的可靠投递，但实际情况下：</strong></p><ul><li><strong>msg:R，msg:A 报文可能丢失：</strong><br>此时直接提示“发送失败”即可，问题不大；</li><li><strong>msg:N，ack:R，ack:A，ack:N这四个报文都可能丢失：</strong><br>此时client-A都收不到期待的ack:N报文，即client-A不能确认client-B是否收到“你好”。</li></ul><h4 id="消息超时重传"><a href="#消息超时重传" class="headerlink" title="消息超时重传"></a>消息超时重传</h4><p>客户端A发送msg:R,收到msg:A后，在期待时间内如果没收到ack:N,client-A会尝试将msg:R重发。可能client-A同时发出了很多消息，故client-A需要在本地维护一个等待ack队列，并配合timer超时机制，来记录哪些消息没有收到ack:N，以定时重发。一旦收到了ack:N，说明client-B收到了“你好”消息，对应的消息将从“等待ack队列”中移除。</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/x2Dy/image-20260214153634044.png" alt="image-20260214153634044"></p><h4 id="消息重传导致的问题"><a href="#消息重传导致的问题" class="headerlink" title="消息重传导致的问题"></a>消息重传导致的问题</h4><p>核心问题就是重复收到</p><p>例如，msg:N报文，ack:N报文都有可能丢失：</p><ul><li><strong>msg:N 报文丢失：</strong>说明client-B之前压根没有收到“你好”报文，超时与重传机制十分有效</li><li><strong>ack:N 报文丢失：</strong>说明client-B之前已经收到了“你好”报文（只是client-A不知道而已），<strong>超时与重传机制将导致client-B收到重复的消息。</strong></li></ul><p>解决方法很简单，<strong>由发送方client-A生成一个消息去重的msgid，保存在“等待ack队列”里</strong>，同一条消息<strong>使用相同的msgid来重传，供client-B去重</strong>，而不影响用户体验。</p><p>1）上述设计理念，由客户端重传，可以保证服务端无状态性（架构设计基本准则）；<br>2）如果client-B不在线，im-server保存了离线消息后，要伪造ack:N发送给client-A；<br>3）离线消息的拉取，为了保证消息的可靠性，也需要有ack机制，但由于拉取离线消息不存在N报文，故实际情况要简单的多，即先发送offline:R报文拉取消息，收到offline:A后，再发送offlineack:R删除离线消息。</p><p>核心总结：可以通过应用层的确认(六个消息)、发送方的超时重传、接收方的去重等手段来保证业务层面消息的不丢不重。</p><p>1）im系统是通过超时、重传、确认、去重的机制来保证消息的可靠投递，不丢不重；<br>2）切记，一个消息的发送，包含上半场msg:R/A/N与下半场ack:R/A/N的6个报文。</p><h3 id="离线消息的可靠投递"><a href="#离线消息的可靠投递" class="headerlink" title="离线消息的可靠投递"></a>离线消息的可靠投递</h3><p>消息接收方不在线时的典型消息发送流程</p><ul><li>Step 1：用户A发送一条消息给用户B；</li><li>Step 2：服务器查看用户B的状态，发现B的状态为“offline”（即B当前不在线）；</li><li>Step 3：服务器将此条消息以离线消息的形式持久化存储到DB中；</li><li>Step 4：服务器返回用户A“发送成功”ACK确认包（<strong>注：</strong>对于消息发送方而言，消息一旦落地存储至DB就认为是发送成功了）</li></ul><p>消息发送出去后，无论是对方实时在线收到还是对方不在线而被服务端离线存储了，对于发送方而言只要消息没有因为网络等原因莫名消失，就应该认为是“被收到了”。</p><p>离线表设计</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 消息接收者ID</span></span><br><span class="line">receiver_uid <span class="type">varchar</span>(<span class="number">50</span>), </span><br><span class="line"> </span><br><span class="line"><span class="comment">-- 消息的唯一指纹码（即消息ID），用于去重等场景，单机情况下此id可能是个自增值、分布式场景下可能是类似于UUID这样的东西</span></span><br><span class="line">msg_id <span class="type">varchar</span>(<span class="number">70</span>), </span><br><span class="line"> </span><br><span class="line"><span class="comment">-- 消息发出时的时间戳（如果是个跨国IM，则此时间戳可能是GMT-0标准时间）       </span></span><br><span class="line">send_time <span class="type">time</span>, </span><br><span class="line"> </span><br><span class="line"><span class="comment">-- 消息发送者ID</span></span><br><span class="line">sender_uid <span class="type">varchar</span>(<span class="number">50</span>), </span><br><span class="line"> </span><br><span class="line"><span class="comment">-- 消息类型（标识此条消息是：文本、图片还是语音留言等）</span></span><br><span class="line">msg_type <span class="type">int</span>, </span><br><span class="line"> </span><br><span class="line"><span class="comment">-- 消息内容（如果是图片或语音留言等类型，由此字段存放的可能是对应文件的存储地址或CDN的访问URL）</span></span><br><span class="line">msg_content <span class="type">varchar</span>(<span class="number">1024</span>), </span><br><span class="line">…</span><br></pre></td></tr></table></figure><p>接收方B要拉取发送方A给ta发送的离线消息，只需在receiver_uid（即接收方B的用户ID), sender_uid（即发送方A的用户ID）上查询，然后把离线消息删除，再把消息返回B即可。</p><figure class="highlight sql"><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"><span class="keyword">SELECT</span>` `msg_id, send_time, msg_type, msg_content</span><br><span class="line"><span class="keyword">FROM</span>` `offline_msgs</span><br><span class="line"><span class="keyword">WHERE</span>` `receiver_uid <span class="operator">=</span> ? ``<span class="keyword">and</span>` `sender_uid <span class="operator">=</span> ?</span><br></pre></td></tr></table></figure><p>整体流程</p><ul><li><p>Stelp 1：用户B开始拉取用户A发送给ta的离线消息；</p></li><li><p>Stelp 2：服务器从DB（或对应的持久化容器）中拉取离线消息；</p></li><li>Stelp 3：服务器从DB（或对应的持久化容器）中把离线消息删除；</li><li>Stelp 4：服务器返回给用户B想要的离线消息。</li></ul><p><strong>优化方案</strong></p><p>如果用户B有很多好友，登陆时客户端需要对所有好友进行离线消息拉取，客户端与服务器交互次数就会比较多。</p><p>例如，对于每个好友，进行一次查询。</p><figure class="highlight arcade"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 登陆时所有好友都要拉取</span></span><br><span class="line"><span class="keyword">for</span><span class="string">``</span>(<span class="built_in">all</span> uid <span class="keyword">in</span> B’s friend-list)&#123;</span><br><span class="line">   <span class="string">``</span><span class="comment">// 与服务器交互</span></span><br><span class="line">   <span class="string">``</span>get_offline_msg(B,uid); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>1.客户端先拉离线消息数量，当用户真正查看时才向服务器发送拉取请求</p><p>2.<strong>一次性拉取所有好友发送给用户B的离线消息</strong>，到<strong>客户端本地再根据sender_uid进行计算</strong>，这样的话，离校消息表的访问模式就变为-&gt;只需要按照receiver_uid来查询了。登录时与服务器的交互次数降低为了1次。</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/eTc3/image-20260214161309225.png" alt="image-20260214161309225"></p><p>用户B一次性拉取所有好友发给ta的离线消息，消息量很大时，一个请求包很大、速度慢，容易卡顿怎么办？</p><p>可以分页拉取：根据业务需求，先拉取最新（或者最旧）的一页消息，再按需一页页拉取，这样便能很好地解决用户体验问题。</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/lS3t/image-20260214161456393.png" alt="image-20260214161456393"></p><p>如何保证可达性，上述步骤第三步执行完毕之后，第四个步骤离线消息返回给客户端过程中，服务器挂点，路由器丢消息，或者客户端crash了，那离线消息岂不是丢了么</p><p>同在线消息的应用层ACK机制一样，离线消息拉时，不能够直接删除数据库中的离线消息，而<strong>必须等应用层的离线消息ACK</strong>，才能删除数据库中的离线消息。这个应用层的ACK可以通过实时消息通道告之服务端，也可以通过服务端提供的REST接口，以更通用、简单的方式通知服务端。</p><p>如果用户B拉取了一页离线消息，却在ACK之前crash了，下次登录时会拉取到重复的离线消息么？</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/Y8vo/image-20260214163239393.png" alt="image-20260214163239393"></p><p>拉取了离线消息却没有ACK，服务器不会删除之前的离线消息，故下次登录时系统层面还会拉取到。但在业务层面，可以<strong>根据msg_id去重</strong>。SMC理论：<strong>系统层面无法做到消息不丢不重，业务层面可以做到，对用户无感知。</strong></p><p>假设有N页离线消息，现在每个离线消息需要一个ACK，那么岂不是客户端与服务器的交互次数又加倍了？有没有优化空间？</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/Yhb8/image-20260214163220532.png" alt="image-20260214163220532"></p><p>不用每一页消息都ACK，在拉取第二页消息时相当于第一页消息的ACK，此时服务器再删除第一页的离线消息即可，最后一页消息再ACK一次（实际上：最后一页拉取的肯定是空返回，这样可以极大地简化这个分页过程，否则客户端得知道当前离线消息的总页数，而由于消息读取延迟的存在，这个总页数理论上并非绝对不变，从而加大了数据读取不一致的可能性）。这样的效果是，不管拉取多少页离线消息，只会多一个ACK请求，与服务器多一次交互。</p><p><strong>常见优化总结如下：</strong></p><ul><li>1）对于同一个用户B，一次性拉取所有用户发给ta的离线消息，再在客户端本地进行发送方分析，相比按照发送方一个个进行消息拉取，能大大减少服务器交互次数；</li><li>2）分页拉取，先拉取计数再按需拉取，是无线端的常见优化；</li><li>3）应用层的ACK，应用层的去重，才能保证离线消息的不丢不重；</li><li>4）下一页的拉取，同时作为上一页的ACK，能够极大减少与服务器的交互次数。</li></ul><h2 id="Timeline逻辑模型下消息的有序"><a href="#Timeline逻辑模型下消息的有序" class="headerlink" title="Timeline逻辑模型下消息的有序"></a>Timeline逻辑模型下消息的有序</h2><p><strong>分布式IM聊天系统中，IM消息怎么做到不丢、不重、还按顺序到达？</strong></p><p><strong>这个问题，涉及到IM系统的两个核心：</strong></p><p><strong><em>1）</em></strong>消息不能丢（可靠性）：比如用户点了发送，不能因为服务宕机或网络抖动，消息石沉大海。比如地铁隧道、电梯间，网络断了又连，消息不能卡住不动（要确保弱网也能用）。</p><p><strong><em>2）</em></strong>顺序不能乱（有序性）：比如“在吗？” 回成 “吗在？”，群聊时间线错乱，体验直接崩盘。</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/K9tb/image-20260214172807014.png" alt="image-20260214172807014"></p><h4 id="为什么消息会乱"><a href="#为什么消息会乱" class="headerlink" title="为什么消息会乱"></a>为什么消息会乱</h4><p>不同消息如果走不同服务节点，走不同网络路径，到达时间完全不可控，最终顺序错乱。</p><p>一个要“串行等”，一个想“并发冲”，天然冲突。</p><p><strong>这时候有人会说：</strong>那我加个全局排序服务不就行了？</p><p><strong>可以，但代价太大</strong>——一个中心节点最多撑几万 QPS，面对百万群聊、亿级用户，还没上线就已过载。</p><p><strong>所以，全局有序不是解，而是枷锁</strong>。我们要的不是“天下大同”，而是“各聊各的别乱就行”。</p><p>所以根本不需要全局有序，只需要“会话内有序”。</p><p>分而治之+局部有序</p><p><strong>具体怎么做？两步走稳：</strong></p><p>第一步 - 业务分区：哈希分片，锁定归属</p><p>用 sessionId 做一致性哈希，确保同一个会话的所有消息始终路由到同一个处理节点。按“会话ID”做哈希，算出该消息该由哪个节点处理。同一会话 → 哈希值一样 → 路由到同一台机器 → 所有消息串行处理，天然避免跨节点乱序。这样一来，单个会话内的消息在服务端就是串行处理的，天然不会乱。</p><p>第二步 - 局部序号：独立发号，局部递增</p><p>每个会话独立维护一个计数器，每来一条消息就+1，作为它的“官方序号”。每个会话,可以配一个独立计数器（比如 Redis 的 INCR），每来一条消息就+1，生成唯一 SEQ。客户端不管什么时候收到消息，只认这个序号，按序号从小到大排列展示。这个 SEQ 就是这条消息的“官方身份证号”，客户端只认这个，不看接收时间。</p><p>1)服务端分片路由</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></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sessionId</span> <span class="operator">=</span> msg.getSessionId();</span><br><span class="line"><span class="comment">//这里是伪代码，实际代码以mq 的负载均衡机制为准 </span></span><br><span class="line"><span class="type">int</span> <span class="variable">nodeIndex</span> <span class="operator">=</span> Math.abs(sessionId.hashCode()) % clusterNodeCount;</span><br><span class="line"> <span class="comment">//这里写个伪代码，代表mq  主从复制</span></span><br><span class="line"><span class="type">ClusterNode</span> <span class="variable">targetNode</span> <span class="operator">=</span> clusterNodes.get(nodeIndex);</span><br><span class="line">targetNode.sendMsg(msg);</span><br></pre></td></tr></table></figure><p><strong>核心就一句：</strong>基于会话 ID 哈希取模，固定路由</p><p>2）服务端序号分配</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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">msgSeq</span> <span class="operator">=</span> redis.incr(<span class="string">&quot;msg_seq_&quot;</span> + sessionId);</span><br><span class="line">msg.setSeq(msgSeq);</span><br><span class="line">msg.setUniqueKey(sessionId + <span class="string">&quot;_&quot;</span> + msgSeq);</span><br></pre></td></tr></table></figure><p>利用Redis 的 INCR，保证同一个会话下的 SEQ 绝对递增，且线程安全。同时用 sessionId_seq 作为唯一键，既能幂等去重，也能防止重试导致消息重复入库。</p><p><strong><em>3）客户端排序逻辑：</em></strong></p><p><strong>最后一步，客户端收尾：</strong>别急着渲染，先排好队。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//这里是伪代码， 先排序</span></span><br><span class="line">List&lt;Msg&gt; sortedMsgs = msgList.stream()</span><br><span class="line">    .sorted(Comparator.comparingLong(Msg::getSeq))</span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"><span class="comment">//这里是伪代码， 再渲染</span></span><br><span class="line">renderMsgList(sortedMsgs);</span><br></pre></td></tr></table></figure><p>无论消息以什么顺序到达，统统按 seq 升序排列后再上屏。</p><p><img data-src="http://www.52im.net/data/attachment/forum/202601/11/203726chtqqstb5ltdbyhj.png" alt="如何保障分布式IM聊天系统的消息有序性（即消息不乱）_2.png"></p><p>对于客户端来说，客户端串行发送（最稳健的方案）</p><p>大多数 IM SDK（如微信、钉钉）在发送消息时，并不是并行的，而是维护了一个<strong>本地发送队列</strong>。</p><ul><li><strong>逻辑</strong>：消息 1 发出后，客户端会进入等待状态。只有收到服务器返回的 <code>SERVER_ACK</code>，或者该消息彻底超时失败，才会从队列取出消息 2 进行发送。</li><li><strong>优点</strong>：从物理上保证了同一时刻只有一个请求在路上，绝对不会出现“后发先至”的情况。</li><li><strong>缺点</strong>：在极差网络下，如果前一条消息卡住，后面的消息会排队。但对于聊天语义来说，这种牺牲是值得的。</li></ul><h3 id="IM聊天系统的消息可靠性保障"><a href="#IM聊天系统的消息可靠性保障" class="headerlink" title="IM聊天系统的消息可靠性保障"></a>IM聊天系统的消息可靠性保障</h3><p>三层兜底，像保险一样层层防</p><p><strong>客户端兜底</strong> —— 消息先存本地，解决网络不稳定问题</p><p><strong>只要没收到 ACK，就当没发成功。</strong></p><p>所以第一步不是联网，而是先把消息塞进手机本地数据库（比如 SQLite）。</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></pre></td><td class="code"><pre><span class="line">db.saveLocalMsg(msg); // 先落库，保命</span><br><span class="line">boolean sendOk = network.send(msg);</span><br><span class="line">if (!sendOk) &#123;</span><br><span class="line">    scheduleRetry(msg, 1000); // 发失败？排队重试</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img data-src="http://www.52im.net/data/attachment/forum/202601/11/203750hcplyxc71fpky1pa.png" alt="如何保障分布式IM聊天系统的消息有序性（即消息不乱）_3.png" style="zoom:67%;" /></p><p><strong>再加上客户端scheduleRetry 采用阶梯式重试策略：</strong></p><ul><li><strong><em>1）</em></strong>第1次失败 → 1秒后重试</li><li><strong><em>2）</em></strong>第2次失败 → 3秒后重试</li><li><strong><em>3）</em></strong>第3次失败 → 5秒后重试</li></ul><p>避免雪崩式刷屏，既保障可靠性，又不压垮服务。只有等到服务端明确说“我收到了”，才把这条消息从本地删掉。</p><p><strong>服务端兜底</strong> 实现 服务端持久化的高可靠</p><p>客户端发来了，服务端能不能直接处理完就返回？<strong>绝对不行！</strong>如果此时机器宕机，消息还在内存里没来得及持久化，那就真的丢了。</p><p><strong>正确做法是两步走：</strong></p><ul><li><strong><em>1）</em></strong>收到消息立刻写入 RocketMQ/数据库（支持刷盘、集群同步）；</li><li><strong><em>2）</em></strong>同步复制到至少3个副本节点，确保单点故障不丢数据。</li></ul><p><strong>伪代码如下：</strong></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></pre></td><td class="code"><pre><span class="line">rocketMQ.send(msg); <span class="comment">// 必须落盘，断电也不怕</span></span><br><span class="line">replicaService.syncTo3Replicas(msg); <span class="comment">// 多副本容灾</span></span><br><span class="line">response.sendAck(msg.getUniqueKey()); <span class="comment">// 此时才能回 ACK</span></span><br></pre></td></tr></table></figure><p><strong>这一步的关键是：</strong>ACK 必须在落盘之后发</p><p><strong>幂等性设计</strong></p><p>网络可能超时、包可能丢失、ACK 可能没传回来。</p><p>用唯一键 + 幂等控制。</p><p>每个消息生成全局唯一的 key（如 sessionID:msgID），服务端通过 Redis 的原子操作判断是否已处理。</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></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">uniqueKey</span> <span class="operator">=</span> msg.getUniqueKey();</span><br><span class="line"><span class="keyword">if</span> (redis.setNx(uniqueKey, <span class="string">&quot;processed&quot;</span>, <span class="number">86400</span>)) &#123;</span><br><span class="line">    processMsg(msg); <span class="comment">// 第一次来，正常处理</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    log.info(<span class="string">&quot;重复消息，忽略：&#123;&#125;&quot;</span>, uniqueKey);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>setNx 是关键：</strong>只有 key 不存在时才设置成功，保证多实例并发下也不会重复消费。</p><p><img data-src="http://www.52im.net/data/attachment/forum/202601/25/160127uxtzsutwwmct3kb4.png" alt="如何保障分布式IM聊天系统的消息可靠性（即消息不丢）_2-1.png" style="zoom:67%;" /></p><h2 id="聊天序列号生成方案"><a href="#聊天序列号生成方案" class="headerlink" title="聊天序列号生成方案"></a>聊天序列号生成方案</h2><p>美团Leaf-Segment和Leaf-Snowflake方案</p><p>生成全局唯一ID的系统ID要求</p><ul><li>1）全局唯一性：不能出现重复的ID号，既然是唯一标识，这是最基本的要求；</li><li>2）趋势递增：在MySQL InnoDB引擎中使用的是聚集索引，由于多数RDBMS使用B-tree的数据结构来存储索引数据，在主键的选择上面我们应该尽量使用有序的主键保证写入性能；</li><li>3）单调递增：保证下一个ID一定大于上一个ID，例如事务版本号、IM聊天中的增量消息、排序等特殊需求；</li><li>4）信息安全：如果ID是连续的，恶意用户的扒取工作就非常容易做了，直接按照顺序下载指定URL即可；如果是订单号就更危险了，竞对可以直接知道我们一天的单量。所以在一些应用场景下，会需要ID无规则、不规则。</li></ul><p>UUID标准型式包含32个16进制数字，以连字号分为五段，形式为8-4-4-4-12的36个字符</p><p><strong>优点：</strong><br>性能非常高：本地生成，没有网络消耗。</p><p><strong>缺点：</strong></p><ul><li>1）不易于存储：UUID太长，16字节128位，通常以36长度的字符串表示，很多场景不适用；</li><li>2）信息不安全：基于MAC地址生成UUID的算法可能会造成MAC地址泄露</li></ul><p><strong>D作为主键时在特定的环境会存在一些问题，比如做DB主键的场景下，UUID就非常不适用：</strong></p><ul><li>① MySQL官方有明确的建议主键要尽量越短越好，36个字符长度的UUID不符合要求：</li><li>② 对MySQL索引不利：如果作为数据库主键，在InnoDB引擎下，UUID的无序性可能会引起数据位置频繁变动，严重影响性能。</li></ul><p>Snowflake算法使用一个 64 bit 的 long 型的数字作为全局唯一 ID。其中 1 个 bit 是不用的，然后用其中的 41 bit 作为毫秒数，用 10 bit 作为工作机器 ID，12 bit 作为序列号。41 bit 可以表示的数字多达 2^41 - 1，也就是可以标识 2 ^ 41 - 1 个毫秒值，换算成年就是表示 69 年的时间。</p><p>这种方式的优缺点如下。</p><p><strong>优点：</strong></p><ul><li>1）毫秒数在高位，自增序列在低位，整个ID都是趋势递增的；</li><li>2）不依赖数据库等第三方系统，以服务的方式部署，稳定性更高，生成ID的性能也是非常高的；</li><li>3）可以根据自身业务特性分配bit位，非常灵活。</li></ul><p><strong>缺点：</strong><br>强依赖机器时钟，如果机器上时钟回拨，会导致发号重复或者服务会处于不可用状态。</p><p>数据库自增ID，这种方案的优缺点如下。</p><p><strong>优点：</strong></p><ul><li>1）非常简单，利用现有数据库系统的功能实现，成本小，有DBA专业维护；</li><li>2）ID号单调自增，可以实现一些对ID有特殊要求的业务。</li></ul><p><strong>缺点：</strong></p><ul><li>1）强依赖DB，当DB异常时整个系统不可用，属于致命问题。配置主从复制可以尽可能的增加可用性，但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号；</li><li>2）ID发号性能瓶颈限制在单台MySQL的读写性能。</li></ul><p><strong>对于MySQL性能问题，可用如下方案解决：</strong>在分布式系统中可以多部署几台机器，每台机器设置不同的初始值，且步长和机器数相等。比如有两台机器。设置步长step为2，TicketServer1的初始值为1（1，3，5，7，9，11…）、TicketServer2的初始值为2（2，4，6，8，10…）。</p><h4 id="Leaf-segment算法"><a href="#Leaf-segment算法" class="headerlink" title="Leaf-segment算法"></a>Leaf-segment算法</h4><p>美团的Leaf-segment方案，实际上是在上面介绍的数据库自增ID方案上的一种改进方案，可生成全局唯一、全局有序的ID，可以用于：事务版本号、IM聊天中的增量消息、全局排序等业务中。</p><p><strong>美团的Leaf-segment对数据库自增ID方案做了如下改变：</strong></p><ul><li>1）原方案每次获取ID都得读写一次数据库，造成数据库压力大。改为利用proxy server批量获取，<strong>每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段</strong>，可以大大的减轻数据库的压力；</li><li>2）各个业务不同的发号需求用biz_tag字段来区分，每个biz-tag的ID获取相互隔离，互不影响。如果以后有性能需求需要对数据库扩容，不需要上述描述的复杂的扩容操作，只需要对biz_tag分库分表就行。</li></ul><figure class="highlight sql"><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="operator">+</span><span class="comment">-------------+--------------+------+-----+-------------------+-----------------------------+</span></span><br><span class="line"><span class="operator">|</span> Field       <span class="operator">|</span> Type         <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span>           <span class="operator">|</span> Extra                       <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------+--------------+------+-----+-------------------+-----------------------------+</span></span><br><span class="line"><span class="operator">|</span> biz_tag     <span class="operator">|</span> <span class="type">varchar</span>(<span class="number">128</span>) <span class="operator">|</span> <span class="keyword">NO</span>   <span class="operator">|</span> PRI <span class="operator">|</span>                   <span class="operator">|</span>                             <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> max_id      <span class="operator">|</span> <span class="type">bigint</span>(<span class="number">20</span>)   <span class="operator">|</span> <span class="keyword">NO</span>   <span class="operator">|</span>     <span class="operator">|</span> <span class="number">1</span>                 <span class="operator">|</span>                             <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> step        <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>)      <span class="operator">|</span> <span class="keyword">NO</span>   <span class="operator">|</span>     <span class="operator">|</span> <span class="keyword">NULL</span>              <span class="operator">|</span>                             <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">desc</span>        <span class="operator">|</span> <span class="type">varchar</span>(<span class="number">256</span>) <span class="operator">|</span> YES  <span class="operator">|</span>     <span class="operator">|</span> <span class="keyword">NULL</span>              <span class="operator">|</span>                             <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> update_time <span class="operator">|</span> <span class="type">timestamp</span>    <span class="operator">|</span> <span class="keyword">NO</span>   <span class="operator">|</span>     <span class="operator">|</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="operator">|</span> <span class="keyword">on</span> <span class="keyword">update</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------+--------------+------+-----+-------------------+-----------------------------+</span></span><br></pre></td></tr></table></figure><p>biz_tag：用来区分业务；<br>max_id：表示该biz_tag目前所被分配的ID号段的最大值；<br>step：表示每次分配的号段长度。</p><p>原来获取ID每次都需要写数据库，现在只需要把step设置得足够大，比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写频率变为1/seq</p><p><strong>优点：</strong></p><ul><li>1）Leaf服务可以很方便的线性扩展，性能完全能够支撑大多数业务场景；</li><li>2）ID号码是趋势递增的8byte的64位数字，满足上述数据库存储的主键要求；</li><li>3）容灾性高：Leaf服务内部有号段缓存，即使DB宕机，短时间内Leaf仍能正常对外提供服务；</li><li>4）可以自定义max_id的大小，非常方便业务从原有的ID方式上迁移过来。</li></ul><p><strong>缺点：</strong></p><ul><li>1）ID号码不够随机，能够泄露发号数量的信息，不太安全；</li><li>2）TP999数据波动大，当号段使用完之后还是会hang在更新数据库的I/O上，tg999数据会出现偶尔的尖刺；</li><li>3）DB宕机会造成整个系统不可用。</li></ul><p>Leaf 取号段的时机是<strong>在号段消耗完的时候进行的，也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间</strong>，并且在这期间进来的请求也会因为DB号段没有取回来，导致线程阻塞。如果请求DB的网络和DB的性能稳定，这种情况对系统的影响是不大的，但是假如取DB的时候网络发生抖动，或者DB发生慢查询就会导致整个系统的响应时间变慢。为此，我们希望DB取号段的过程能够做到无阻塞，不需要在DB取号段的时候阻塞请求线程，即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。</p><h4 id="Leaf-snowflake"><a href="#Leaf-snowflake" class="headerlink" title="Leaf-snowflake"></a>Leaf-snowflake</h4><p>Leaf-segment方案可以生成趋势递增的ID，同时ID号是可计算的，但不适用于订单ID生成场景。比如竞对在两天中午12点分别下单，通过订单id号相减就能大致计算出公司一天的订单量。面对这一问题，美团技术团队实现了 Leaf-snowflake这个方案。</p><p>Leaf-snowflake方案是Twittersnowflake改进版，它完全沿用snowflake方案的bit位设计（如上图所示），即是“1+41+10+12”的方式组装ID号。</p><p>对于workerID的分配，当服务集群数量较小的情况下，完全可以手动配置。Leaf服务规模较大，动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。</p><ul><li>1）启动Leaf-snowflake服务，连接Zookeeper，在leaf_forever父节点下检查自己是否已经注册过（是否有该顺序子节点）；</li><li>2）如果有注册过直接取回自己的workerID（zk顺序节点生成的int类型ID号），启动服务；</li><li>3）如果没有注册过，就在该父节点下面创建一个持久顺序节点，创建成功后取回顺序号当做自己的workerID号，启动服务。</li></ul><p>除了每次会去ZK拿数据以外，也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题，恰好机器出现问题需要重启时，能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA。</p><p><strong>服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点：</strong></p><ul><li>1）若写过，则用自身系统时间与leaf_forever/${self}节点记录时间做比较，若小于leaf_forever/${self}时间则认为机器时间发生了大步长回拨，服务启动失败并报警；</li><li>2）若未写过，证明是新服务节点，直接创建持久节点leaf_forever/${self}并写入自身系统时间，接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确，具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP：Port，然后通过RPC请求得到所有节点的系统时间，计算sum(time)/nodeSize；</li><li>3）若abs( 系统时间-sum(time)/nodeSize ) &lt; 阈值，认为当前系统时间准确，正常启动服务，同时写临时节点leaf_temporary/${self} 维持租约；</li><li>4）否则认为本机系统时间发生大步长偏移，启动失败并报警；</li><li>5）每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。</li></ul><h2 id="多点登录和消息漫游"><a href="#多点登录和消息漫游" class="headerlink" title="多点登录和消息漫游"></a>多点登录和消息漫游</h2><p>以微信为例：可以PC端、phone端同时登录、同时收发消息。在任何一个终端的任何一个实例登录qq，都能够拉取到所有历史聊天消息，这个就是消息漫游。</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/Q1gu/image-20260214195714405.png" alt="image-20260214195714405"></p><ul><li>1）用户A登录在gate1上，发出消息；</li><li>2）gate1将消息给logic/router；</li><li>3）logic/router查询接收方的在线状态（B在线，C不在线）；</li><li>4）例如接收方C不在线，存储离线；</li><li>4）例如接收方B在线，且登录在gate2上，消息投递给gate2；</li><li>5）gate2将消息投递给B。</li></ul><p>接收方多点登录：pc登录、phone也登录，后一端登录不会将前一端踢出，<strong>cache中存储状态与登录点时，不再以user_id为key，改为以user_id+终端类型为key即可</strong>。</p><p>当用户A（phone端）给用户B发送消息时，除了要投递给B的所有多点登录端，还需要投递给A自已多点登陆的其他端（pc端），如上图中步骤4与步骤5。只有这样，才能在所有用户的所有端，恢复与还原双方聊天的上下文。</p><p>如果业务不需要支持“消息漫游”的功能，对于在线消息，如果用户实时接收到则是不需要存储到数据库的。但如果要支持“换一台机器（指的是用户的客户端）也能看到历史的聊天消息”，就需要对所有消息进行存储了。</p><p><img data-src="http://www.52im.net/data/attachment/forum/201706/18/224634ss4oxs0uao58z055.jpg" alt="浅谈移动端IM的多点登陆和消息漫游原理_4.jpg"><br>消息投递如上图：用户A发送消息给用户B，虽然B在线，仍然要增加一个步骤2.5，在投递之前进行存储，以备B的其他端登陆时，可以拉取到历史消息。</p><p><img data-src="http://www.52im.net/data/attachment/forum/201706/18/224709xc6u5nx66puq9vzz.jpg" alt="浅谈移动端IM的多点登陆和消息漫游原理_5.jpg"><br>消息拉取如上图：原本不在线的B(phone端)，又重新登录了，他怎么拉取历史消息？只需要在客户端本地存储一个上一次拉取到的msg_id(time)，到服务端重新拉取即可。这里还有个问题：由于服务端存储所有消息成本是非常高的，所以一般“消息漫游”是有时间（或者消息数）限制，不能拉取所有所有几年前的历史消息，比如只能拉取3个月内的云端消息等。</p><p><strong>“多点登录”是指多个端同时登录一个帐号，同时收发消息，关键点是：</strong></p><ul><li>1）需要在服务端存储同一个用户多个端的状态与登陆点；</li><li>2）发出消息时，要对发送方的多端与接收端的多端，都进行消息投递。</li></ul><p><strong>“消息漫游”是指一个用户在任何端，都可以拉取到历史消息，关键点是：</strong></p><ul><li>1）所有消息存储在云端；</li><li>2）每个端<strong>本地存储last_msg_id，在登录时可以到云端同步历史消息</strong>；</li><li>3）云端存储所有消息成本较高，一般会对历史消息时间(或者条数)进行限制。</li></ul><h2 id="消息撤回的实现"><a href="#消息撤回的实现" class="headerlink" title="消息撤回的实现"></a>消息撤回的实现</h2><ol><li>撤回指令的发送与校验</li></ol><p>当用户 A 点击“撤回”时，客户端并不是发送“删除请求”，而是发送一条类型为 <code>MSG_REVOKE</code> 的消息包。</p><ul><li><strong>数据包内容</strong>：包含 <code>operatorId</code>（操作者）、<code>sessionId</code>（会话ID）以及最重要的 <strong><code>targetMsgId</code>（要撤回的那条消息的唯一ID）</strong>。</li><li><strong>服务端校验</strong>：<ul><li><strong>权限校验</strong>：只有发送者本人（或群管理员）可以撤回。</li><li><strong>时效校验</strong>：检查当前时间与原消息发送时间的差值（如超过 2 分钟则拦截并返回错误码）。</li></ul></li></ul><ol><li>服务端的“逻辑覆盖”</li></ol><p>服务端接收到撤回指令后，不会执行物理删除（<code>DELETE</code>），而是执行<strong>逻辑更新</strong>：</p><ol><li><strong>修改数据库</strong>：在消息表中找到该 <code>msgId</code>，将其 <code>msgType</code> 修改为 <code>REVOKED</code>，并清空原有的文字/图片内容，改为“对方撤回了一条消息”。</li><li><strong>更新快照（Session_Snapshot）</strong>：如果这条消息是该会话的最后一条，需要同步更新 Redis 中的会话快照，将首页显示的摘要改为“对方撤回了一条消息”。</li><li><p><strong>生成撤回信令</strong>：服务端会生成一条带有新 <code>SeqId</code> 的撤回通知，分发给会话中的所有参与者。</p></li><li><p>客户端的实时更新（UI 层）</p></li></ol><p>当客户端（包括发送方和接收方）收到这条 <code>REVOKED</code> 类型的指令时：</p><ul><li><strong>内存替换</strong>：根据指令中的 <code>targetMsgId</code>，在当前聊天列表的内存缓存（List/Array）中找到那条旧消息。</li><li><strong>UI 刷新</strong>：将旧的消息气泡替换为一个灰色的提示条（“对方撤回了一条消息”）。</li><li><strong>本地持久化</strong>：同步更新本地 SQLite 数据库，确保下次打开 App 时，看到的也是撤回后的状态。</li></ul><h2 id="群聊消息的有序不丢失"><a href="#群聊消息的有序不丢失" class="headerlink" title="群聊消息的有序不丢失"></a>群聊消息的有序不丢失</h2><p>两个群业务的核心数据结构：</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line">群成员表：用来描述一个群里有多少成员</span><br><span class="line">t_group_users(group_id, user_id)</span><br><span class="line">群离线消息表：用来描述一个群成员的离线消息</span><br><span class="line">t_offine_msgs(user_id, group_id, sender_id,<span class="type">time</span>, msg_id, msg_detail)</span><br></pre></td></tr></table></figure><p>群消息投递</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/Knc4/image-20260214204421041.png" alt="image-20260214204421041"></p><ul><li>步骤1：群消息发送者x向server发出群消息；</li><li>步骤2：server去db中查询群中有多少用户(x,A,B,C,D)；</li><li>步骤3：server去cache中查询这些用户的在线状态；</li><li>步骤4：对于群中在线的用户A与B，群消息server进行实时推送；</li><li>步骤5：对于群中离线的用户C与D，群消息server进行离线存储</li></ul><p>群离线消息拉取</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/y9Rd/image-20260214204502274.png" alt="image-20260214204502274"></p><ul><li>步骤1：离线消息拉取者C向server拉取群离线消息；</li><li>步骤2：server从db中拉取离线消息并返回群用户C；</li><li>步骤3：server从db中删除群用户C的群离线消息。</li></ul><p>缺点：对于同一份群消息的内容，多个离线用户存储了很多份。假设群中有200个用户离线，离线消息则冗余了200份，这极大的增加了数据库的存储压力</p><p>优化1 <strong>减少存储量</strong></p><p>为了减少离线消息的冗余度，增加一个群消息表，用来存储所有群消息的内容，离线消息表只存储用户的群离线消息msg_id，就能大大的降低数据库的冗余存储量</p><figure class="highlight stylus"><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></pre></td><td class="code"><pre><span class="line">群消息表：用来存储一个群中所有的消息内容</span><br><span class="line"><span class="function"><span class="title">t_group_msgs</span><span class="params">(group_id, sender_id, time,msg_id, msg_detail)</span></span></span><br><span class="line">群离线消息表：优化后只存储msg_id</span><br><span class="line"><span class="function"><span class="title">t_offine_msgs</span><span class="params">(user_id, group_id, msg_id)</span></span></span><br></pre></td></tr></table></figure><p><strong>这样优化后，群在线消息发送就做了一些修改：</strong></p><ul><li>步骤3：每次发送在线群消息之前，要先存储群消息的内容；</li><li>步骤6：每次存储离线消息时，只存储msg_id，而不用为每个用户存储msg_detail。</li></ul><p><strong>拉取离线消息时也做了响应的修改：</strong></p><ul><li>步骤1：先拉取所有的离线消息msg_id；</li><li>步骤3：再根据msg_id拉取msg_detail；</li><li>步骤5：删除离线msg_id。</li></ul><p><strong>存在的问题（如同单对单消息的发送一样）：</strong></p><ul><li>1）在线消息的投递可能出现消息丢失，例如服务器重启，路由器丢包，客户端crash；</li><li>2）离线消息的拉取也可能出现消息丢失，原因同上。</li></ul><p>优化2 应用层ACK</p><p><img data-src="https://files.seeusercontent.com/2026/02/14/6Aor/image-20260214205637816.png" alt="image-20260214205637816" style="zoom: 67%;" /></p><ul><li>步骤3：在消息msg_detail存储到群消息表后，不管用户是否在线，都先将msg_id存储到离线消息表里；</li><li>步骤6：<strong>在线的用户A和B收到群消息后，需要增加一个应用层ACK</strong>，来标识消息到达；</li><li>步骤7：在线的用户A和B在应用层ACK后，将他们的离线消息msg_id删除掉。</li></ul><p><strong>对应到群离线消息的拉取也一样：</strong></p><ul><li>步骤1：先拉取msg_id；</li><li>步骤3：再拉取msg_detail；</li><li>步骤5：最后应用层ACK；</li><li>步骤6：server收到应用层ACK才能删除离线消息表里的msg_id。</li></ul><p>对于离线的每一条消息，虽然只存储了msg_id，但是每个用户的每一条离线消息都将在数据库中保存一条记录</p><p>优化3 离线消息表 只需要存储最近一条拉取到的离线消息的time（或者msg_id），下次登录时拉取在那之后的所有群消息即可，而完全没有必要存储每个人未拉取到的离线消息msg_id。</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line">群成员表：用来描述一个群里有多少成员，以及每个成员最后一条ack的群消息的msg_id（或者<span class="type">time</span>）</span><br><span class="line">t_group_users(group_id, user_id, last_ack_msg_id(last_ack_msg_time))</span><br><span class="line">群消息表：用来存储一个群中所有的消息内容，不变</span><br><span class="line">t_group_msgs(group_id, sender_id, <span class="type">time</span>,msg_id, msg_detail)</span><br><span class="line">群离线消息表：不再需要了</span><br></pre></td></tr></table></figure><p><strong>离线消息表优化后，群在线消息的投递流程：</strong></p><ul><li>步骤3：在消息msg_detail存储到群消息表后，不再需要操作离线消息表（优化前需要将msg_id插入离线消息表）；</li><li>步骤7：在线的用户A和B在应用层ACK后，将last_ack_msg_id更新即可（优化前需要将msg_id从离线消息表删除）</li></ul><p><strong>群离线消息的拉取流程也类似：</strong></p><ul><li>步骤1：拉取离线消息；</li><li>步骤3：ACK离线消息；</li><li>步骤4：更新last_ack_msg_id。</li></ul><p>由于“消息风暴扩散系数”的存在，假设1个群有500个用户，“每条”群消息都会变为500个应用层ACK，将对服务器造成巨大的冲击，有没有办法减少ACK请求量呢？</p><p>优化4 批量ACK</p><p><strong>批量ACK的方式又有两种：</strong></p><ul><li>1）每收到N条群消息ACK一次，这样请求量就降低为原来的1/N了；</li><li>2）每隔时间间隔T进行一次群消息ACK，也能达到类似的效果。</li></ul><p><strong>新的问题：</strong>批量ACK有可能导致：还没有来得及ACK群消息，用户就退出了，这样下次登录会拉取到重复的离线消息。<br><strong>解决方案：</strong>msg_id去重，不对用户展现，保证良好的用户体验。</p><p><strong>还可能存在的问题：</strong>群离线消息过多：拉取过慢。 <strong>解决方案：</strong>分页拉取（按需拉取）,</p><h2 id="时序性和一致性"><a href="#时序性和一致性" class="headerlink" title="时序性和一致性"></a>时序性和一致性</h2><p><strong>即时消息的时序性（Message Ordering）</strong>，简单来说就是保证消息的<strong>“发生顺序”</strong>与<strong>“呈现顺序”</strong>完全一致。</p><p>为什么分布式环境下即时消息时序性难以保证</p><ol><li>时钟不一致:分布式环境下，有多个客户端、有web集群、service集群、db集群，他们都分布在不同的机器上，机器之间都是使用的本地时钟，而没有一个所谓的“全局时钟”，所以不能用“本地时间”来完全决定消息的时序。</li><li>多客户端:绝对时序上，APP1先发出msg1，APP2后发出msg2，都发往服务器web1，网络传输是不能保证msg1一定先于msg2到达的，所以即使以一台服务器web1的时间为准，也不能精准描述msg1与msg2的绝对时序。</li><li>服务集群:绝对时序上，web1先发出msg1，后发出msg2，由于网络传输及多接收方的存在，无法保证msg1先被接收到先被处理，故也无法保证msg1与msg2的处理时序。</li><li>网络传输与多线程:多发送方与多接收方都难以保证绝对时序，假设只有单一的发送方与单一的接收方，能否保证消息的绝对时序呢？结论是悲观的，由于网络传输与多线程的存在，仍然不行。</li></ol><p>假设只有一个发送方，一个接收方，上下游连接只有一条连接池，通过阻塞的方式通讯，难道不能保证先发出的消息msg1先处理么？</p><p>可以，但吞吐量会非常低，而且单发送方单接收方单连接池的假设不太成立，高并发高可用的架构不会允许这样的设计出现</p><p><strong>生产环境下的优化方案：</strong></p><ol><li>以客户端或者服务端的时序为准:多客户端、多服务端导致“时序”的标准难以界定，需要一个标尺来衡量时序的先后顺序。</li><li>服务端能够生成单调递增的id:利用单点写db的seq/auto_inc_id肯定能生成单调递增的id，只是说性能及扩展性会成为潜在瓶颈。对于严格时序的业务场景，可以利用服务器的单调递增id来保证时序。</li><li>大部分业务能接受误差不大的趋势递增id</li><li>单聊中业务上不需要全局消息一致，只需要对于同一个发送方A，ta发给B的消息时序一致就行，常见优化方案，在A往B发出的消息中，加上发送方A本地的一个绝对时序，来表示接收方B的展现时序。</li><li>群聊中保证各接收方收到顺序一致:service层不再需要去一个统一的后端拿全局seq，而是在service连接池层面做细小的改造，保证一个群的消息落在同一个service上，这个service就可以用本地seq来序列化同一个群的所有消息，保证所有群友看到消息的时序是相同的。</li></ol><p>保证发送端的物理序（即用户点击发送的顺序 $M1 \rightarrow M2$ 能够原样到达服务器）是 IM 系统的基本功。</p><p>在生产级框架（如微信 SDK、网易云信、钉钉等）中，这并不是通过单一手段实现的，而是通过<strong>“客户端串行队列”</strong>和<strong>“逻辑序号标记”</strong>双重机制来保证的。</p><ol><li>核心实现方案：客户端发送队列 (Serial Sending Queue)</li></ol><p>这是最主流的生产级实现方式。客户端 SDK 内部并不是直接调用网络接口发送消息，而是维护了一个<strong>“待发送队列”</strong>。</p><ul><li><strong>执行逻辑</strong>：<ol><li>用户点击发送 $M1$，SDK 将其放入队列，状态设为“发送中”。</li><li>SDK 发出 $M1$ 的网络请求，<strong>此时不发送 $M2$</strong>。</li><li>只有当收到服务器对 $M1$ 的 <code>SERVER_ACK</code>，或者 $M1$ 彻底重试失败后，SDK 才会从队列取出 $M2$ 进行下发。</li></ol></li><li><strong>保证了什么</strong>：它在<strong>物理链路上</strong>保证了同一时刻只有一个消息在“飞行”。这样在服务器看来，接收到的顺序一定等于用户发送的顺序。</li></ul><ol><li>逻辑序号标记：ClientSeq</li></ol><p>为了应对极端情况（比如网络切换导致旧连接没断、新连接又开了），生产框架会给每个消息包打上一个<strong>本地递增的 <code>ClientSeq</code></strong>。</p><ul><li><strong>实现方式</strong>：在客户端本地数据库或内存里维护一个自增变量。</li><li><strong>作用</strong>：即使因为并发或网络重传导致 $M2$ 比 $M1$ 先到，服务端可以根据 <code>ClientSeq</code> 发现：“咦，我收到了序号 2，但序号 1 还没到”。</li><li><strong>服务端处理</strong>：服务端可以选择等待一会（针对极高要求的业务），或者在最终给消息分配全局 <code>SeqID</code> 时参考这个 <code>ClientSeq</code>。</li></ul><h2 id="聊消息的已读回执功能该怎么实现"><a href="#聊消息的已读回执功能该怎么实现" class="headerlink" title="聊消息的已读回执功能该怎么实现"></a>聊消息的已读回执功能该怎么实现</h2><p>群消息设计，群消息存一份，为每个成员设置一个群消息队列，会有大量数据冗余，并不合适</p><p>如果群消息只存一份，怎么知道每个成员读了哪些消息？可以利用群消息的偏序关系，记录每个成员的last_ack_msgid（last_ack_time），这条消息之前的消息已读，这条消息之后的消息未读。该方案意味着，对于群内的每一个用户，只需要记录一个值即可。</p><p><strong>群消息表：</strong>记录群消息</p><p>group_msgs(msgid, gid, sender_uid, time, content);</p><p>各字段的含义为：消息ID，群ID，发送方UID，发送时间，发送内容。</p><p><strong>群成员表：</strong>记录群里的成员，以及每个成员收到的最后一条群消息</p><p>group_users(gid, uid, last_ack_msgid);</p><p>各字段的含义为：群ID，群成员UID，群成员最后收到的一条群消息ID</p><p><strong>其整个消息发送的流程如上图：</strong></p><ul><li>1）A发出群消息；</li><li>2）server收到消息后，一来要将群消息落地，二来要查询群里有哪些群成员，以便实施推送；</li><li>3）对于群成员，查询在线状态；</li><li>4）对于在线的群成员，实施推送。</li></ul><p>这个流程里，只要第二步消息落地完成，就能保证群消息不会丢失。</p><p><strong>核心问题：</strong>如何保证接收方一定收到群消息？<br><em>**</em>各个收到消息后，要修改各群成员的last_ack_msgid，以告诉系统，这一条消息确认收到了。</p><p>对于在线的群友，收到群消息后，第一时间会ack、修改last_ack_msgid。对于离线的群友，会在下一次登录时，拉取未读的所有群离线消息，并将last_ack_msgid修改为最新的一条消息。</p><p>如果ack丢失，群友会不会拉取重复的群消息？<br>会，可以<strong>根据msgid在客户端本地做去重</strong>，即使系统层面收到了重复的消息，仍然可以保证良好的用户体验。</p><p>上述流程，只能确保接收方收到消息，发送方<strong>仍然不知道哪些人在线阅读了消息，哪些人离线未阅读消息</strong>，并没有实现已读回执，那已读回执会对系统设计产生什么样的影响呢？</p><p>对于发送方发送的任何一条群消息，都需要知道，这条消息有多少人已读多少人未读，就需要一个基础表来记录这个关系。</p><p><strong>消息回执表：</strong>用来记录消息的已读回执</p><p>msg_acks(sender_uid, msgid, recv_uid, gid,if_ack);</p><p>增加了已读回执逻辑后，群消息的流程会有细微的改变,<strong>接着，server收到消息后，除了要：</strong></p><ul><li>1）将群消息落地；</li><li>2）查询群里有哪些群成员，以便实施推送；</li></ul><p><strong>之外，还需要：</strong></p><ul><li>3）插入每条消息的初始回执状态。</li></ul><p><strong>接收方修改last_ack_msgid的流程，会变为：</strong></p><ul><li>1）发送ack请求；</li><li>2）修改last_ack_msgid，并且，修改已读回执if_ack状态；</li><li>3）<strong>查询发送方在线状态</strong>；</li><li>4）向发送方实时推送已读回执（如果发送方在线）；</li></ul><p><strong>如果发送方不在线，ta会在下次登录的时候：</strong></p><ul><li>5）从关联表里拉取每条消息的已读回执。</li></ul><p><strong>这里的初步结论是：</strong></p><ul><li>如果发送方在线：会实时被推送已读回执；</li><li>如果发送方不在线：会在下次在线时拉取已读回执。</li></ul><p>群消息已读回执的“消息风暴扩散系数”，假设每个群有200个用户，其中20%的用户在线，即40各用户在线。</p><p><strong>那么，群用户每发送一条群消息，会有：</strong></p><ul><li>40个消息，通知给群友；</li><li>40个ack修改last_ack_msgid，发给服务端；</li><li>40个已读回执，通知给发送方。</li></ul><p>群数量，群友数量，群消息数量越来越多之后，存储也会成为问题。</p><p><strong>群消息的推送，能否改为接收方轮询拉取？</strong><br><strong>答：</strong>不能，消息接收，实时性是核心指标。</p><p><strong>对于last_ack_msgid的修改，真的需要每个群消息都进行ack么？</strong><br><strong>答：</strong>其实不需要，<strong>可以批量ack，累计收到N条群消息（例如10条），再向服务器发送一次last_ack_msgid的修改请求，同时修改这个请求之前所有请求的已读回执</strong>，这样就能将40个发送给服务端的ack请求量，降为原来的1/10。</p><p><strong>会带来什么副作用？</strong><br><strong>答：</strong>last_ack_msgid的作用是，记录接收方最近新取的一条群消息，如果不实时更新，可能导致，异常退出时，有一些群消息没来得及更新last_ack_msgid，使得下次登陆时，会拉取到重复的群消息。但这不是问题，客户端可以根据msgid去重，用户体验不会受影响。</p><p><strong>发送方在线时，对于已读回执的发送，真的需要实时推送么？</strong><br><strong>答：</strong>其实不需要，<strong>发送方每发一条消息，会收到40个已读回执，采用轮询拉取（例如1分钟一次，一个小时也就60个请求），可以大大降低请求量</strong>。</p><p><strong>会带来什么副作用？</strong><br><strong>答：</strong>已读回执更新不实时，最坏的情况下，1分钟才更新回执。当然，可以根据性能与产品体验来折衷配置这个轮询时间。</p><p><strong>如何降低数据量？</strong><br><strong>答：</strong>回执数据不是核心数据</p><ul><li>已读的消息，可以进行物理删除，而不是标记删除；</li><li>超过N长时间的回执，归档或者删除掉。</li></ul><p><strong>对于群消息已读回执，一般来说：</strong></p><ul><li>如果发送方在线，会实时被推送已读回执；</li><li>如果发送方不在线，会在下次在线时拉取已读回执。</li></ul><p><strong>如果要对进行优化，可以：</strong></p><ul><li>接收方累计收到N条群消息再批量ack；</li><li>发送方轮询拉取已读回执。</li></ul><h2 id="在线状态同步的推与拉"><a href="#在线状态同步的推与拉" class="headerlink" title="在线状态同步的推与拉"></a>在线状态同步的推与拉</h2><h3 id="保证单聊好友状态的一致性"><a href="#保证单聊好友状态的一致性" class="headerlink" title="保证单聊好友状态的一致性"></a>保证单聊好友状态的一致性</h3><p>用户uid-A登录时，如何获取自己全部好友的在线状态？</p><p>1）服务器要存储所有用户的在线状态（往往存储在保证高可用的缓存集群里）</p><p>2）用户状态实时变更，任何用户登录时，需要将服务端自己的在线状态置为online；任何用户登出时，需要将服务端自己的状态置为offline -&gt; 保证服务端状态存储的一致性与实时性</p><p>3）uid-A登录时，先去数据库拉取自己的好友列表，再去缓存获取所有好友的在线状态 -&gt; 保证登录时好友状态获取的一致性与实时性</p><p>用户uid-A的好友uid-B状态改变时（由登录、登出、隐身等动作触发），uid-A如何知道这一事件</p><p><strong>方案一的逻辑：</strong><br>uid-A向服务器轮询拉取uid-B（其实是自己的全部好友）的状态，例如每1分钟一次。</p><p><strong>方案一的缺点：</strong></p><ul><li>如果uid-B的状态改变，uid-A获取不实时，可能有1分钟时延；</li><li>如果uid-B的状态不改变，uid-A会有大量无效的轮询请求，占用服务器资源。</li></ul><p><strong>方案二的逻辑：</strong><br>uid-B状态改变时（由登录、登出、隐身等动作触发），服务器不仅在缓存中修改uid-B的状态，还要将这个状体改变的通知推送给uid-B的在线反向好友</p><p><strong>方案二的优点：</strong>实时。<br><strong>方案二的缺点：</strong>当在线好友量很大时，任何一个用户状态的改变，会扩散成N个实时通知，这个N叫做“消息风暴扩散系数”。<br>设一个im系统平均每个用户有200个反向好友，平均有20%的反向好友在线，那么消息风暴扩散系数N=40，这意味着，任何一个状态的变化会变成40个推送请求。</p><p>1场景一：群友状态一致性有什么不同，和好友状态一致性相比复杂在哪里？为什么不能采用实时推送？</p><p>理论上群友状态也可以通过实时推送的方式实现，以保证实时性。但实际上，群友状态一般都是采用拉取的方式获得，因为群友状态“消息风暴扩散系数”N实在太大，全部实时获取系统往往承受不了。</p><p>假设平均每个用户加了20个群，平均每个群有200个用户，依然假设20%的用户在线，那么为了保证群友状态的实时性，每个用户登录，就要将自己的状态改变通知发送给20<em>200</em>20%=800个群友，N=800，意味着，任何一个状态的变化会变成800个推送请求。</p><p>XXX系统使用的是群友状态推送，不存在的这样的问题？那很可能是，XXX系统的用户量和活跃度还不够高吧。</p><p>2场景二：轮询拉取群友状态也会给服务器带来过大的压力，还有什么优化方式？</p><p>群友的数据量太大，虽然每个用户平均加入了20个群，但实际上并不会每次登录都进入每一个群。不采用轮询拉取，而<strong>采用按需拉取，延时拉取的方式，在真正进入一个群时才实时拉取群友的在线状态</strong>，是既能满足用户需求（用户感觉是状态是实时、一致的，但其实是进入群才拉取的），又能降低服务器压力。这是一种常见方法。</p><h2 id="心跳机制与断线重连机制"><a href="#心跳机制与断线重连机制" class="headerlink" title="心跳机制与断线重连机制"></a>心跳机制与断线重连机制</h2><p>在使用 TCP 长连接的 IM 服务设计中，往往都会涉及到心跳。<strong>心跳一般是指某端(绝大多数情况下是客户端)每隔一定时间向对端发送自定义指令，以判断双方是否存活</strong>，因其按照一定间隔发送，类似于心跳，故被称为心跳指令。</p><p>为什么说基于TCP的移动端IM仍然需要心跳保活？使用 TCP 长连接来实现业务的最大驱动力在于：在当前连接可用的情况下，<strong>每一次请求都只是简单的数据发送和接受</strong>，免去了 DNS 解析，连接建立等时间，大大加快了请求的速度，同时也有利于接受服务器的实时消息。但前提是连接可用。</p><p>如果连接无法很好地保持，每次请求就会变成撞大运：运气好，通过长连接发送请求并收到反馈。运气差，当前连接已失效，请求迟迟没有收到反馈直到超时，又需要一次连接建立的过程，其效率甚至还不如 HTTP。而连接保持的前提必然是检测连接的可用性，并在连接不可用时主动放弃当前连接并建立新的连接。基于这个前提，必须要有一种机制用于检测连接可用性。同时移动网络的特殊性也要求客户端需要在空余时间发送一定的信令，避免连接被回收。</p><p>而对于服务器而言，能够及时获悉连接可用性也非常重要：一方面服务器需要及时清理无效连接以减轻负载，另一方面也是业务的需求，如游戏副本中服务器需要及时处理玩家掉线带来的问题。</p><p>TCP KeepAlive 的机制其实并不适用于此。Keep Alive 机制开启后，TCP 层将在定时时间到后发送相应的 KeepAlive 探针以确定连接可用性。一般时间为 7200 s，失败后重试 10 次，每次超时时间 75 s。显然默认值无法满足我们的需求。因为 TCP KeepAlive 是用于检测连接的死活，而心跳机制则附带一个额外的功能：检测通讯双方的存活状态。两者听起来似乎是一个意思，但实际上却大相径庭。</p><p>考虑一种情况，某台服务器因为某些原因导致负载超高，CPU 100%，无法响应任何业务请求，但是使用 TCP 探针则仍旧能够确定连接状态，这就是典型的连接活着但业务提供方已死的状态，对客户端而言，这时的最好选择就是断线后重新连接其他服务器，而不是一直认为当前服务器是可用状态，一直向当前服务器发送些必然会失败的请求。</p><p>从上面我们可以知道，KeepAlive 并不适用于检测双方存活的场景，<strong>这种场景还得依赖于应用层的心跳。应用层心跳有着更大的灵活性，可以控制检测时机，间隔和处理流程，甚至可以在心跳包上附带额外信息。从这个角度而言，应用层的心跳的确是最佳实践。</strong></p><p>应用层心跳的确是检测连接有效性，双方是否存活的最佳实践，那么剩下的问题就是怎么实现。</p><p>最简单粗暴做法当然是定时心跳，如每隔 30 秒心跳一次，15 秒内没有收到心跳回包则认为当前连接已失效，断开连接并进行重连。这种做法最直接，实现也简单。唯一的问题是比较耗电和耗流量。以一个协议包 5 个字节计算，一天收发 2880 个心跳包，一个月就是 5 <em> 2 </em> 2880 * 30 = 0.8 M 的流量，如果手机上多装几个 IM 软件，每个月光心跳就好几兆流量没了，更不用说频繁的心跳带来的电量损耗。</p><p>既然频繁心跳会带来耗电和耗流量的弊端，改进的方向自然是减少心跳频率，但也不能过于影响连接检测的实时性。基于这个需求，一般可以将心跳间隔根据程序状态进行调整，当程序在后台时(这里主要考虑安卓)，尽量拉长心跳间隔，5 分钟、甚至 10 分钟都可以。</p><p>而当 App 在前台时则按照原来规则操作。连接可靠性的判断也可以放宽，<strong>避免一次心跳超时就认为连接无效的情况，使用错误积累，只在心跳超时 n 次后才判定当前连接不可用。当然还有一些小 trick 比如从收到的最后一个指令包进行心跳包周期计时而不是固定时间</strong>，这样也能够一定程度减少心跳次数</p><p><strong>使用Netty的IdleStateHandler实现心跳机制</strong></p><p>所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性。<em>**</em>心跳包还有另一个作用，经常被忽略，即：一个连接如果长时间不用，防火墙或者路由器就会断开该连接</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">IdleStateHandler</span><span class="params">(<span class="type">int</span> readerIdleTimeSeconds, <span class="type">int</span> writerIdleTimeSeconds, <span class="type">int</span> allIdleTimeSeconds)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>((<span class="type">long</span>)readerIdleTimeSeconds, (<span class="type">long</span>)writerIdleTimeSeconds, (<span class="type">long</span>)allIdleTimeSeconds, TimeUnit.SECONDS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLE 的 IdleStateEvent 事件.</li><li>writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLE 的 IdleStateEvent 事件.</li><li>allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLE 的 IdleStateEvent 事件.</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;在规定时间内未收到客户端的任何数据包, 将主动断开该连接&lt;/p&gt;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ServerIdleStateTrigger</span> <span class="keyword">extends</span> <span class="title class_">ChannelInboundHandlerAdapter</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">if</span> (evt <span class="keyword">instanceof</span> IdleStateEvent) &#123;</span><br><span class="line">            <span class="type">IdleState</span> <span class="variable">state</span> <span class="operator">=</span> ((IdleStateEvent) evt).state();</span><br><span class="line">            <span class="keyword">if</span> (state == IdleState.READER_IDLE) &#123;</span><br><span class="line">                <span class="comment">// 在规定时间内没有收到客户端的上行数据, 主动断开连接</span></span><br><span class="line">                ctx.disconnect();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>.userEventTriggered(ctx, evt);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="IM系统消息未读数实现"><a href="#IM系统消息未读数实现" class="headerlink" title="IM系统消息未读数实现"></a>IM系统消息未读数实现</h2><p><strong>实现思路大致如下：</strong></p><p><strong><em>1）</em></strong>每发一个消息，消息接收者的会话未读数+1，并且接收者所有未读数+1；</p><p><strong><em>2）</em></strong>消息接收者返回消息接收确认ack后，消息未读数会-1；</p><p><strong><em>3）</em></strong>消息接收者的未读数+1，服务端就会推算有多少条未读数的通知。</p><p><strong>分布式锁保证总未读数和会话未读数一致：</strong></p><p><strong><em>1）</em></strong>原因：当总未读数增加，这个时候客户端来了请求将未知数置0，然后再增加会话未读数，那么会导致不一致；</p><p><strong><em>2）</em></strong>保证：为了保证总未读数和会话未读数原子性，需要用分布式锁来保证。</p><p>群聊消息未读数的难点和优化思路</p><p><strong>对于群聊来说，消息未读数的技术难点主要是：</strong>一个群聊每秒几百的并发聊天，比如消息未读数，相当于每秒W级别的写入redis，即便redis做了集群数据分片+主从，但是写入还是单节点，会有写入瓶颈。</p><p>按群ID分组或者用户ID分组，批量写入，写入的两种方式：定时flush和满多少消息进行flush。</p><p><a href="https://blog.csdn.net/2303_77208351/article/details/143084326">即时通讯 : 未读消息计数_获取未读消息数量-CSDN博客</a></p><h2 id="即时通讯的消息传输安全原理"><a href="#即时通讯的消息传输安全原理" class="headerlink" title="即时通讯的消息传输安全原理"></a>即时通讯的消息传输安全原理</h2><p>对称加密：DES算法</p><p>DES即数据加密标准，这种加密算法是由IBM研究提出来的， 是一种分组密码，它用于对64比特的数据进行加密和解密。DES算法所用的密钥也是64比特，但由于其中包含了8个比特的奇偶校验位，因而实际的密钥长度是56比特。DES算法多次组合替代算法和换位算法，利用分散和错乱的相互作用，把明文编制成密码强度很高的密文。DES算法的加密和解密的流程是完全相同的，区别仅仅是加密与解密使用子密钥序列的顺序正好相反n1。DES算法属于<strong>对称加密算法，即加密和解密共享同一个密钥，主要用于解决数据机密性问题。</strong></p><p>公开密钥算法：RSA算法</p><p>RSA算法作为惟一被广泛接受并实现的通用公共密钥加密方法，是众多阐述非对称密码体制的算法中最具代表性的，几乎成了公开密钥密码学的同义词。它是麻省理工大学的Rivest，Shamir和Adleman（RSA算法即为三人名字的缩写）于1977年研制并于1978年首次发表的一种算法。该算法的数学基础是数论的欧拉定理，它的安全性依赖于大数的因子分解的困难性，该算法至今仍没有发现严重的安全漏洞。R<strong>SA使用两个密钥，一个是公钥(PubHc Key)，另一个是私钥(Private Key)加密时把明文分戍块，块的大小可变</strong>，但不超过密钥的长度。RSA把明文块转化为与密钥长度相同的密文。RSA算法还可以用于“数字签名”，即用私钥进行加密，公钥来解密。</p><p>Hash算法：MD5算法</p><p>MD5算法并不是加密算法，但却能形成信息的数字“指纹”，主要用途是确保数据没有被篡改或变化过，以保证数据的完整性。MD5算法有三个特性：</p><ul><li>a）能处理任意大小的信息，并生成固定长度128位的信息摘要；</li><li>b）具有不可预见性，信息摘要的大小与原始信息的大小没有任何联系，原信息的每一个微小变化都会对信息摘要产生很大的影响；</li><li>c）具有不可逆性，没有办法通过信息摘要直接恢复原信息。</li></ul><h4 id="中级通信安全"><a href="#中级通信安全" class="headerlink" title="中级通信安全"></a>中级通信安全</h4><p><img data-src="https://files.seeusercontent.com/2026/02/17/6qOz/image-20260217143701726.png" alt="image-20260217143701726"></p><ul><li>客户端和服务端提前约定好加密算法，在传递消息前，先协商密钥；</li><li>客户端，请求密钥；</li><li>服务端，返回密钥；</li><li><p>然后用协商密钥加密消息，传输密文。</p></li><li><p><strong>安全评估：</strong>首先，网上传输的内容是不安全的，于是乎，黑客能得到加密key=X。其次，客户端和服务端提前约定的加密算法是不安全的，于是乎，黑客能得到加密算法。于是乎，黑客截取后续传递的密文，可以用对应的算法和密钥解密；</p></li><li><strong>改进方案：</strong>协商的密钥不能在网络上传递。</li></ul><h4 id="高级通信安全"><a href="#高级通信安全" class="headerlink" title="高级通信安全"></a>高级通信安全</h4><p><img data-src="https://files.seeusercontent.com/2026/02/17/5Fiu/image-20260217145154678.png" alt="image-20260217145154678"></p><ul><li>协商的密钥无需在网络传输；</li><li>使用“具备用户特性的东西”作为加密密钥，例如：用户密码的散列值；</li><li>一人一密，每个人的密钥不同；</li><li>然后密钥加密消息，传输密文；</li><li><p>服务端从db里获取这个“具备用户特性的东西”，解密。</p></li><li><p><strong>安全评估：</strong>用户客户端内存是安全的，属于黑客远端范畴，不能被破解。当然，用户中了木马，用户的机器被控制的情况不在此列，如果机器真被控制，监控用户屏幕就好了，就不用搞得这么麻烦了；</p></li><li><strong>导致后果：</strong>使用“具备用户特性的东西”作为加密密钥，一人一密，是安全的。只是，当“具备用户特性的东西”泄漏，就有潜在风险；</li></ul><h4 id="一次一密-密钥协商"><a href="#一次一密-密钥协商" class="headerlink" title="一次一密 密钥协商"></a>一次一密 密钥协商</h4><p>每次通信前，进行密钥协商，一次一密。密钥协商过程，如上图所述，需要随机生成三次密钥，两次非对称加密密钥（公钥，私钥），一次对称加密密钥，简称安全信道建立的“三次握手”，在客户端发起安全信道建立请求后：</p><ul><li>服务端随机生成公私钥对(公钥pk1，私钥pk2)，并将公钥pk1传给客户端：<br>(注意：此时黑客能截获pk1)；</li><li>客户端随机生成公私钥对(公钥pk11，私钥pk22)，并将公钥pk11，通过pk1加密，传给服务端：<br>(注意：此时黑客能截获密文，也知道是通过pk1加密的，但由于黑客不知道私钥pk2，是无法解密的)；</li><li>服务端收到密文，用私钥pk2解密，得到pk11；</li><li>服务端随机生成对称加密密钥key=X，用pk11加密，传给客户端：<br>(注意：同理，黑客由密文无法解密出key)；</li><li>客户端收到密文，用私钥pk22解密，可到key=X。</li></ul><p>至此，安全信道建立完毕，后续通讯用key=X加密，以保证信息的安全性。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://zhuanlan.zhihu.com/p/31377253">现代IM系统中聊天消息的同步和存储方案探讨 - 知乎</a></li><li><a href="http://www.52im.net/thread-4257-1-1.html">跟着源码学IM(十一)：一套基于Netty的分布式高可用IM详细设计与实现(有源码)-IM开发/专项技术区 - 即时通讯开发者社区!</a></li><li><a href="https://vankykoo.cn/posts/im-如何保证消息有序性/">【IM】如何保证消息有序性？ | VankyHub</a></li><li><a href="http://www.52im.net/thread-561-1-1.html">高性能网络编程(一)：单台服务器并发TCP连接数到底可以有多少-网络编程/专项技术区 - 即时通讯开发者社区!</a></li><li><a href="https://uaxe.github.io/geektime-docs/后端-架构/即时消息技术剖析与实战/即时消息技术剖析与实战/">即时消息技术剖析与实战 - 极客时间文档</a></li><li><a href="http://www.52im.net/thread-1221-1-1.html">移动端IM中大规模群消息的推送如何保证效率、实时性？-IM开发/专项技术区 - 即时通讯开发者社区!</a></li><li><a href="http://www.52im.net/thread-1230-1-1.html">现代IM系统中聊天消息的同步和存储方案探讨-IM开发/专项技术区 - 即时通讯开发者社区!</a></li><li><a href="https://help.aliyun.com/zh/tablestore/use-cases/message-system-model-in-the-modern-im-system">基于Timeline模型构建IM消息系统的同步与存储架构-表格存储-阿里云</a></li><li><a href="http://www.52im.net/thread-464-1-1.html">新手入门一篇就够：从零开发移动端IM-IM开发/专项技术区 - 即时通讯开发者社区!</a></li><li><a href="https://www.cnblogs.com/sunshineliulu/p/11553448.html">《即时消息技术剖析与实战》学习笔记7——IM系统的消息未读 - 鹿呦呦 - 博客园</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/02/13/%E5%88%86%E5%B8%83%E5%BC%8FIM%E8%81%8A%E5%A4%A9%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0/</id>
    <link href="https://www.sekyoro.top/2026/02/13/%E5%88%86%E5%B8%83%E5%BC%8FIM%E8%81%8A%E5%A4%A9%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0/"/>
    <published>2026-02-13T06:26:03.000Z</published>
    <summary>
      <![CDATA[<p>搜集到了关于现代即时通讯(IM)系统中一些设计方案，作为分布式集群应用的集大成者，这里进行总结学习。这些资料基本来自52im.net。在此感谢站长和作者们。<br>]]>
    </summary>
    <title>分布式IM聊天系统学习</title>
    <updated>2026-02-17T09:33:35.731Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="AI" scheme="https://www.sekyoro.top/tags/AI/"/>
    <category term="娱乐" scheme="https://www.sekyoro.top/tags/%E5%A8%B1%E4%B9%90/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近字节跳动的 <strong>SeedDance</strong> 再次刷爆社媒。虽然作为闭源模型，其底层架构是否彻底从 DiT（Diffusion Transformer）进化到了更高效的架构尚不可知，但一个明确的趋势是：AI 商业化应用在经历了过去几年的技术积淀后，正迎来真正意义上的落地爆发。</p><p>作为程序员或内容创作者，我们的选择变多了，但认知的边界也在不断扩张。在这些层出不穷的应用中，我们需要警惕那些夸大其词的包装，真正的应用盈利点仍需深挖，工具的长期价值仍需时间检验。</p><span id="more"></span><p>早期的 AI 分类习惯于按<strong>文本、图像、视频、声音</strong>来切分，但随着原生多模态的成熟，按<strong>任务场景</strong>来区分变得更为科学。</p><h3 id="任务导向：对话助手向“全能中枢”演进"><a href="#任务导向：对话助手向“全能中枢”演进" class="headerlink" title="任务导向：对话助手向“全能中枢”演进"></a>任务导向：对话助手向“全能中枢”演进</h3><p>2026年的基础对话助手已不再仅仅是聊天机器人，而是演变为个人与企业知识流的中央调度引擎。这一领域由少数几家拥有超大规模计算资源和数据集支持的科技巨头主导，由于训练成本和技术门槛的持续抬升，个人或中小型团队通过开源方式完全复制顶尖闭源模型能力的难度已进一步加大 。</p><p>在通用对话助手领域，Gemini、DeepSeek、ChatGPT、豆包、千问等已进入“诸神黄昏”般的红海竞争。</p><p>GPT-5.1被广泛认为是目前最强大的全能型选手，尤其在复杂逻辑推理和通用任务调度方面具有显著优势 。与之竞争的Claude 4.6（包括Opus和Sonnet变体）则进一步巩固了其在长文本处理、安全性以及政策敏感型场景中的声誉，它在处理深层文档理解和持续数小时的复杂对话中表现出极高的稳定性 。</p><p>Google的Gemini 3则凭借其海量的上下文窗口和深度的生态集成脱颖而出。Gemini 3能够原生处理数百万级别的Token，使其成为研究密集型任务和大规模代码库分析的首选 。此外，Google在图像生成能力的深度整合上也采取了独特策略，Gemini在输出图像时会调用专门的“Nano Banana”模型（即Gemini 3 Pro Image），这种模型架构实现了推理脑（Reasoning Brain）与生成手（Generation Hand）的有机结合 。 </p><p>随着企业对AI投入产出比（ROI）的关注度提升，2026年出现了一个显著趋势：模型编排（Model Orchestration）。应用开发者不再盲目追求单一最强模型，而是根据任务难度进行动态路由。例如，简单的日常问答会被分流到成本极低的Claude 4.5 Haiku或Gemini Flash，而涉及核心商业逻辑的任务才会动用GPT-5.1等昂贵的顶尖模型 。这种策略反映了AI应用从“尝鲜”向“精细化运营”的转变 </p><p>大厂的护城河在于超大规模数据集训练带来的“涌现”能力。例如 Gemini 在输出高质量图像时会联动 <strong>Nano Banana</strong>（Google 的原生视觉模型），这种深度的底层集成是目前开源社区单纯通过“套壳”难以复制的。</p><p>对于<strong>写作助手</strong>，我曾尝试过 <a href="https://writesonic.com/">Writesonic</a> 和 <a href="https://rytr.me/">rytr.me</a>。它们正从简单的改写、续写，进化为集资料搜索（RAG 增强）、信源验证、SEO 优化及自动配图于一体的全流程创作终端，极大解决了创作者“搜集资料慢”的痛点。</p><h3 id="编码革命：从-Copilot-到自主-Agent"><a href="#编码革命：从-Copilot-到自主-Agent" class="headerlink" title="编码革命：从 Copilot 到自主 Agent"></a>编码革命：从 Copilot 到自主 Agent</h3><p>代码生成模型（Coding LLMs）是生产力转化最显著的领域。</p><p>目前，Claude 4.5/5、GPT-5以及Gemini 3在编码能力上处于第一梯队，而国内的Qwen Coder和GLM系列在特定语境和高性价比需求下也占有重要份额 。在2026年的实际应用中，开发者更倾向于根据模型对代码库上下文的理解深度来选择工具。例如，Claude Code被广泛认为是2026年最顶尖的命令行编码助手，它以智能体的形式直接运行在终端中，能够自主执行搜索文件、编辑代码、运行测试并提交Git记录的一站式操作 。</p><ul><li><strong>模型梯队</strong>：目前处于第一梯队的是 Claude 4.5、GPT-5 以及 Gemini 3 系列，国产的 GLM 和 Qwen Coder 也表现出极强的本地化适配能力。</li><li><strong>生态协议</strong>：值得关注的是 <strong>MCP (Model Context Protocol)</strong> 协议的普及。现在的 IDE 如 Cursor、Trae 不仅仅是搜索上下文，而是通过 MCP 实现跨工具的 Skills 调用，让 AI 真正具备了操作本地数据库、调用 API 甚至管理服务器的能力。Cursor已确立其行业标准的地位，它不再只是带有补全功能的编辑器，而是通过深度索引整个代码库，实现了对复杂项目重构的自动化支持 。与此竞争的Trae由字节跳动推出，凭借其完全免费的策略和“思考后再行动（Think-before-doing）”的构建模式，吸引了大量追求高性价比和稳定方案的开发者 </li><li><strong>在线 Agent</strong>：<a href="https://bolt.new/">Bolt.new</a> 和 <a href="https://v0.app/">v0.app</a> 代表了“对话即部署”的新范式，前者强于全栈工程，后者则在前端组件化、原子化设计上无可匹敌。非技术人员也可以通过自然语言描述，在数分钟内构建出具备前端交互和简单后端的全栈应用。v0.app在前端UI设计和React组件生成上的专业度，使其成为UI工程师快速原型设计的首选工具 。</li></ul><div class="table-container"><table><thead><tr><th><strong>编码工具/模型</strong></th><th><strong>核心优势</strong></th><th><strong>2026年关键特性</strong></th><th><strong>适合人群</strong></th></tr></thead><tbody><tr><td>Claude Code</td><td>终端原生智能体</td><td>能够自主规划多步任务并执行CLI命令</td><td>高级工程师、运维专家</td></tr><tr><td>Cursor</td><td>深度代码库索引</td><td>Composer模式支持跨文件的大型架构修改</td><td>职业开发者</td></tr><tr><td>Trae</td><td>思考型构建逻辑</td><td>完全免费，基于VS Code生态，提示词遵循度高</td><td>个人开发者、初学者</td></tr><tr><td>v0.app</td><td>前端极致优化</td><td>实时渲染React组件，卓越的UI审美</td><td>前端工程师、UI设计师</td></tr></tbody></table></div><h3 id="视觉艺术：开源自由与自然语言编辑"><a href="#视觉艺术：开源自由与自然语言编辑" class="headerlink" title="视觉艺术：开源自由与自然语言编辑"></a>视觉艺术：开源自由与自然语言编辑</h3><p>图像领域是开源社区的“自留地”，其灵活性远超闭源工具。</p><ul><li><strong>技术基石</strong>：从 Stable Diffusion 到目前的 <strong>FLUX.2</strong>，配合 <strong>ComfyUI</strong> 这种节点式流工作，给予了开发者极高的定制上限。此外，<a href="https://github.com/Tongyi-MAI/Z-Image">Z-Image</a> 和 <a href="https://seed.bytedance.com/en/seedream4_0">Seedream</a> 等新锐模型的加入，让国产图像生态也日益繁荣。</li><li><strong>编辑范式</strong>：图像模型已从最初的“随机开盲盒”进化到了精准控制。利用 <strong>FLUX Kontext</strong>，我们可以直接用自然语言指令（In-painting）进行局部修改，不再需要复杂的 ControlNet 蒙版操作。</li><li><strong>场景垂直化</strong>：如专门深耕漫画领域的 <a href="https://anifusion.ai/">Anifusion</a>，以及目前全球最大的模型权重社区 <a href="https://civitai.com/">Civitai</a>，它们将技术与具体业务场景实现了完美闭环。</li></ul><p>开源社区在2026年依然是创新的源头。FLUX.2和ComfyUI的组合为高级用户提供了近乎无限的自定义空间 。特别值得注意的是，Civitai已进化为一个成熟的AI绘画社区，不仅提供模型下载，还成为了技术与应用场景完美融合的枢纽 。在小众业务领域，诸如Anifusion这样的应用展示了AI与垂直行业的深度结合。Anifusion利用专门微调的LoRA模型，帮助漫画家在保持角色形象完全一致的前提下，自动化完成草稿上色、背景生成及分镜布局，显著提升了漫画产出的效率 。</p><p>Google的“Nano Banana Pro”在2026年成为了企业级设计的标杆。它引入了先进的文字渲染技术，能够准确地在海报或UI原型中生成多语言文字，彻底解决了此前AI绘图“乱码”的问题 。更重要的是，它具备“世界知识”，能够根据实时数据（如气象信息）生成科学准确的图表和示意图 。</p><p>字节跳动推出的Seedream 4.5（国内对应即梦AI）则在Cinematic（电影感）和Mood（氛围感）上独树一帜。Seedream 4.5将生成与编辑统一在单一架构下，用户可以通过自然语言指令直接修改已有图像的局部细节，如“将这个角色的蓝色夹克换成大红色”或“将背景的光影调至黄金时段”，这种精准的局部控制能力使得它在商业广告设计中备受青睐 。</p><p>针对追求速度和低成本的场景，阿里巴巴推出的Z-Image Turbo在2026年成为“性价比之王”。其参数规模仅为60亿（6B），却能在普通消费级显卡上实现亚秒级的图像生成，且在双语文字处理和光影真实度上达到了顶尖水平，非常适合需要大规模生成图片素材的内容创作者 </p><h3 id="声音智能：成熟的音频生成与音乐创作"><a href="#声音智能：成熟的音频生成与音乐创作" class="headerlink" title="声音智能：成熟的音频生成与音乐创作"></a>声音智能：成熟的音频生成与音乐创作</h3><p>声音技术已从“像人说话”演进到“有感情地演唱”。目前包括音色克隆，声音定制，语音转换(主要是AI翻唱)，以及文本生成语音。</p><ul><li><p><strong>TTS 与 SVC</strong>：开源界的<a href="https://github.com/svc-develop-team/so-vits-svc">so-vits-svc</a>和<a href="https://github.com/RVC-Boss/GPT-SoVITS">RVC-Boss/GPT-SoVITS</a>依然是定制化音色的首选。TTS有<a href="https://github.com/index-tts/index-tts">index-tts/index-tts</a>，<a href="https://github.com/QwenLM/Qwen3-TTS以及[microsoft/VibeVoice](https://github.com/microsoft/VibeVoice)。专门的厂商有[MiniMax">https://github.com/QwenLM/Qwen3-TTS以及[microsoft/VibeVoice](https://github.com/microsoft/VibeVoice)。专门的厂商有[MiniMax</a> Audio&amp;Music](<a href="https://www.minimax.io/audio)。微软的">https://www.minimax.io/audio)。微软的</a> <strong>VibeVoice</strong> 则在文本转语音的情感丰富度上再次拔高了行业天花板。从语音转换（SVC）到文本转语音（TTS），开源模型如GPT-SoVITS和RVC-Boss已经实现了极高的人声还原度 。</p></li><li><p><strong>音乐创作</strong>：<strong>Suno</strong> 是目前的绝对主流，而网易云的 <strong>X Studio</strong> 则在中文语境和虚拟歌手表现力上更胜一筹。</p><blockquote><p><strong>Tip</strong>：生成高质量音乐的核心在于<strong>结构化提示词</strong>。利用 <code>[Intro]</code>, <code>[Chorus]</code>, <code>[Bridge]</code>, <code>[Outro]</code> 等元标签控制 BGM 的起承转合，效果远好于单一的形容词堆砌。</p></blockquote></li><li><p><strong>曲风探索</strong>：如果你对曲风定位模糊，<a href="https://www.music-map.com/">Music-Map</a> 是寻找相似风格的最佳辅助。</p></li></ul><p>针对如何生成想要的音乐，也有一套简单流程，本质类似对话以及编程得提示词构造。</p><p><strong>提示词结构</strong></p><figure class="highlight scheme"><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 class="name">Intro</span>]</span><br><span class="line">[<span class="name">Soft</span> piano melody starts, gentle and slow]</span><br><span class="line"></span><br><span class="line">[<span class="name">Verse</span> <span class="number">1</span>]</span><br><span class="line">(<span class="name">Instrumental</span> - Piano and light violin)</span><br><span class="line"></span><br><span class="line">[<span class="name">Chorus</span>]</span><br><span class="line">(<span class="name">Instrumental</span> - Orchestral swell, emotional strings)</span><br><span class="line"></span><br><span class="line">[<span class="name">Bridge</span>]</span><br><span class="line">(<span class="name">Minimalist</span> piano, echoing notes)</span><br><span class="line"></span><br><span class="line">[<span class="name">Outro</span>]</span><br><span class="line">[<span class="name">Piano</span> fades out slowly]</span><br><span class="line">[<span class="name">End</span>]</span><br></pre></td></tr></table></figure><p>可以使用一些微调得模型辅助生成，chatgpt以及moe商店中包含。类似图像生成，也有一些专门的提示词网站<a href="https://sunoprompt.com/zh">Suno 提示词</a>。</p><p>针对大众音乐创作，Suno V4.5/V5 依然是首选工具，其核心优势在于“ songwriter”导向，能快速生成具有情感爆发力的完整歌曲 。而 Udio 2 则被视为“工程师”的乐器，它提供了更细粒度的控制，允许用户对乐曲的混音、声部和音质进行专业调节，生成的音频更具“录音室感”，少有AI常见的“电音质感”</p><h3 id="视频模型：迈向“世界模型”的最后堡垒"><a href="#视频模型：迈向“世界模型”的最后堡垒" class="headerlink" title="视频模型：迈向“世界模型”的最后堡垒"></a>视频模型：迈向“世界模型”的最后堡垒</h3><p> 视频模型是AI应用中提升空间最大、也最具应用潜力的领域。尽管技术上仍面临分镜逻辑和音画完全同步的挑战，但目前的顶尖模型已能产出接近广播级的短视频素材。</p><p>目前，视频生成正从“像素预测”向“<strong>世界模型</strong>（World Models）”进化。尽管 <a href="https://www.veo-3.org/home">Veo 3 AI </a>、SeedDance 1.5 Pro（即梦 AI）、可灵、海螺等工具已能生成极具电影感的画面，但在长镜头的一致性、复杂物理规律（如液体流动、碰撞）的模拟上仍有改进空间。但不可否认，AI 视频已完全具备了作为商业短视频素材的能力。最近因为<a href="[即梦AI ](https://jimeng.jianying.com/ai-tool/home">Seedance1.5pro</a>)上线了又刷了一波存在感。</p><p>Google的Veo 3.1在2026年引入了“成分转视频（Ingredients to Video）”技术，允许创作者上传参考图片，并以该图片为基准生成风格一致的高质量视频 。它支持原生的9:16垂直画幅输出，完美契合TikTok等短视频平台的需求，并提供4K上倍增技术，确保画面细节达到影院级标准 </p><p>字节跳动推出的Seedance 1.5 Pro（即梦1.5 Pro）则在“视听协同”上取得了突破。它是首个大规模应用“音画同步联合生成”架构的模型，这意味着视频中的人物说话时，口型与发音是原生同步的，且环境音（如脚步声、爆炸声）会随着画面动态自动生成且具有空间感 。此外，它支持复杂的镜头语言控制，如希区柯克变焦（Dolly Zoom）和长镜头跟踪，这极大地降低了专业视频制作的门槛 </p><p><strong>视频生成模型对比</strong></p><div class="table-container"><table><thead><tr><th><strong>模型名称</strong></th><th><strong>核心优势</strong></th><th><strong>分辨率/帧率</strong></th><th><strong>关键技术特性</strong></th></tr></thead><tbody><tr><td>Veo 3.1</td><td>极高的物理真实感与4K画质</td><td>4K @ 30fps (Upscaled)</td><td>场景扩展技术，支持超过60秒的连贯叙事</td></tr><tr><td>Seedance 1.5 Pro</td><td>卓越的口型同步与电影分镜</td><td>1080p @ 60fps</td><td>联合音画生成架构，支持多语言对白</td></tr><tr><td>Kling 2.6 (可灵)</td><td>强大的长视频处理能力</td><td>1080p</td><td>复杂的物理规律模拟，更长的单次生成时长</td></tr><tr><td>Sora 2</td><td>艺术表现力与世界模拟</td><td>4K</td><td>顶尖的光影追踪与一致性表现</td></tr></tbody></table></div><h3 id="工作流与-Agent：边界的探索"><a href="#工作流与-Agent：边界的探索" class="headerlink" title="工作流与 Agent：边界的探索"></a>工作流与 Agent：边界的探索</h3><p><a href="https://autoglm.zhipuai.cn/">AutoGLM</a><strong>、</strong>Coze和n8n等工具正尝试将 AI 嵌入复杂的业务流中。最近备受关注的 <a href="https://docs.openclaw.ai/zh-CN">OpenClaw</a> 则展示了 <strong>LAM (Large Action Model)</strong> 的潜力。目前的痛点在于：如何精准划定 Agent 的能力边界，确保其在自主调用权限时的安全性与确定性。</p><p>作为一个本地运行的个人AI智能体，它彻底改变了用户与计算机交互的方式 。与云端助手不同，OpenClaw具有以下特点：  </p><ul><li><strong>全系统访问：</strong> 它直接运行在用户的终端上，可以读取文件、执行脚本、管理日程，甚至通过WhatsApp或Slack直接与用户交流 。  </li><li><strong>隐私与安全：</strong> 所有操作在本地沙盒环境中执行，虽然这带来了极大的便利，但也引发了关于“超级权限漏洞”的激烈讨论。专家提醒用户必须严格监控其权限边界，以防恶意指令执行 。  </li><li><strong>自演化能力：</strong> OpenClaw可以通过编写自己的代码来扩展新技能，这种“自我改进”的特性使其被视为通向通用人工智能（AGI）的一步试验 </li></ul><p>在工作学习上 我也推荐Notebook LLM,它包括网络中搜索，对话，音频以及思维导图中，是非常好用的工具。还包含分析，测验等。另外Google还有<a href="https://ai.google.dev/aistudio">Google AI Studio</a>进行Agent模式构建应用。</p><p><img data-src="https://img.proanimer.com/imgs/image-20260211225327620.png" alt=""></p><p>简单总结一下，目前专门针对底层模技术的大创新已经减少了，更多地在构建应用上。比如Dify,Coze等构建工作流定时完成某些任务等。但除此之外，也有社交媒体相关的工具，比如AI剪辑视频、AI文字配图等，方便内容创作者产出，这种相当于在原本的工具上利用AI辅助，不能完全叫做AI工具。</p><p>而在使用上，<strong>海外方案</strong>：推荐 <strong>Google 一件套</strong>（Gemini + Nano Banana + Veo + NotebookLM），这种闭环生态的协作效率极高。</p><p><strong>国内选型</strong>：推荐<strong>通义全家桶</strong>（包括通义万相、视频模型等），在中文语境和视觉审美上更贴合本土需求。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://www.bilibili.com/video/BV1ibvhBqEmp/?spm_id_from=333.337.search-card.all.click&amp;vd_source=177ef88aa6608bc3652c72d71b0aa098">年度好用AI大赏</a></li><li><a href="https://zhuanlan.zhihu.com/p/1887930075674048214">Suno AI使用教程：从入门到进阶(超详细) </a></li><li><a href="https://www.iconpik.com/16-tts-models-comparative-analysis/">2025年16款主流TTS模型评测</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/02/11/AI%E5%B7%A5%E5%85%B7%E5%B0%9D%E9%B2%9C/</id>
    <link href="https://www.sekyoro.top/2026/02/11/AI%E5%B7%A5%E5%85%B7%E5%B0%9D%E9%B2%9C/"/>
    <published>2026-02-11T13:22:06.000Z</published>
    <summary>
      <![CDATA[<p>最近字节跳动的 <strong>SeedDance</strong> 再次刷爆社媒。虽然作为闭源模型，其底层架构是否彻底从 DiT（Diffusion Transformer）进化到了更高效的架构尚不可知，但一个明确的趋势是：AI 商业化应用在经历了过去几年的技术积淀后，正迎来真正意义上的落地爆发。</p>
<p>作为程序员或内容创作者，我们的选择变多了，但认知的边界也在不断扩张。在这些层出不穷的应用中，我们需要警惕那些夸大其词的包装，真正的应用盈利点仍需深挖，工具的长期价值仍需时间检验。</p>]]>
    </summary>
    <title>AI工具尝鲜:从&quot;模态爆发&quot;到&quot;生态耕耘&quot;</title>
    <updated>2026-02-11T16:07:23.783Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="Java" scheme="https://www.sekyoro.top/tags/Java/"/>
    <category term="JVM" scheme="https://www.sekyoro.top/tags/JVM/"/>
    <category term="并发编程" scheme="https://www.sekyoro.top/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="抱歉，您输入的密码错误，请检查后重新输入。" data-whm="抱歉, 当前文章不能被校验, 不过您还是可以看看解密后的内容。">  <script id="hbeData" type="hbeData" data-hmacdigest="6228d590bd877a1bd6ce6edb86e574002d011920e26a23f909189eaee3e03b40">36f29f3a8a3599dfb9e50adddc71ef17f889143c3729484ef1b08a49ece73bd816e66da72ae603bd8a0f855afe7c9aef72a837931d792debc1a09d35d3f033e99e4088d7bcc5cc519beb311785d8c8bb96560133cba6d19aa6d724c5c4b1974a854769800190f094cc39d518e07b53cd5f52df1800ad3744c8c627edddc8a50f5e3ad0ae6ab292104b0d2872fd298ac97355dfd16a5db02d519dbdd2874ca6710827f0df536913be78d83ac60ecc72ee9a6664ece86b63516e25cbd68e80975c38a4216d325dbdc9508687703bb1ce079fa1e6298d5ca1747855f1a1abb91c75c96bd3530fe3785fa3bc99a016bf4d5e0b29c8e345f8bfcad22554751707379db427b47ee9971abf92fadc3de4130ff7595da5c26bd9b2b8d654acf7c1583a19213114d651040682821b65bd8751cab2e0d1841794944600358accb4b49a3ba65d5b96272ed4f893eef05d94ddd8d0b988fc3390f8318416cc092be3533e19d2f641f81b89e57bedb7f936ef02db310648f7870131762fd492c8a7f34b5b9f452772d76e1cf61ca823d506d5b20b7c7d9637cd5f8de500ae180e8aa4d588e81e41044bc1f246e93beb4db90ce3cb0d6d4336c57711f18ac4272a6b524f7fcdece03696e0932e37037ce6cde9a49652ab5ae38c117063afda6ee1ef0badbdb76516e44d1f2ebc2ac52ccf13c4a1a14f327c9cb027656ca7edf8c50258273b002ac81a531badf4173f78ea20241ec3437114bef0a84b156652ac007783a25344d1418df7d51f0921b06582c626163b800014cc61d107dea76e4c8b89b7355a352302926171e3b7ae8b8a5dc216fd8d64f118977662cf16c578c21bba639b9bddb576312540ce96ad6a354828ea472fadf46bf0f65cb0e30db116a62e6263cb55322df42d7d78fb99fdc1eab4082cd024eefb9d83f2e9d7935a9f8958ac0e7d2723be67e9f6a36bbf256bb42db2d5443bdaeeafdf13df22e39a2cb503eadb34c54b80fb9d4e5fafede7dee3a36474d00be5bd70ddcb11c932b4c5be0dfbb3051dadfe481822edb686327494042c69f9370ef927444ea163b5647907ce79316880d0abd1bd76bdac97ada39fb34d4d0b71445d49f38e3732a52f39f310f1dd5645bd3f55b2adf2497dee603190d750fc8024e8f5e02307b04a45024d7c73181fb179cb939eb09bec8214f4eb84524852693a70e3b4bf2f3d95b12b1dd7ae1d638f703239c8c97b6b0328ad969e5910b4284a7f1cc2d47c78cd42476f107120815603b413d0b7efd4189f4ddd8574954563abdd67c6c63418c486dd8b32b3b9fe2a27893412c33ddb1b046899320746731043912789b18b9935ac1b1d17dcd7f9ad39941d1faa5eceaeb2f4d69828879b75fec65cd68a7ce3922f662775c0a6ca895b479c8e8f01f981668f3f3d481e3157a54ab563a671c61866afc8e3e59f3da909c91c3fda6fe6e1dd494cd1da24c380cc3abd737fa9323d4e6972d098841b5aedbf000c7c3131587a25b0a0a9e860811c017783ae9ad160b91bb8c955155be7b4c164a16722bab0d98f851de5016eaaceb58bafcde6e992361b7a52acc9893b46280a0712bef3f3345db3de19925f2cca5bc783d15108d8dfce05e6cfefa65c4ce5de2fbdde9387c6312f8c5ff7780d0e5001c678ad0e2b106453a5a7c72b09e4c5c5b2ebe82a247ff5e30b7b8dec141f466cb208e60b9f84b19d415107111ce97dfe931c2adfa4c3a6ca5f80f49fa4ec3de6f9f2cc38d3a65c9b6cb2c4a9a719129bc9e67ef811ae8498bb1ec0459375f5d33a609998bdcc09db08d87874f998e98e3154c32a50f40e533db44cb4ed08f1de1e79d7f0e70d1ad22e2c156def3abad2022a391e416e1b1a46a2a77245a51e6961a8bab7ae586171063082670e930cc9c026fd28a234e8d296f91eb4eeacc8b95e81a994179e940f8211cdb92d2c977414ed0ee647dc0ab5ea0c7ff018ae1ad6291a7dc996bfc23eb26ac2134d16fff17240967aea87237050bc696acf0f6d62105bae0425b3634194023c191cc000db26cd3d49c472c202f08dc3d735fc6ea0151ec4448f5589c2eac5978a9f25ed389139623568987fc4843ec427bb3799f6c18fded510787487856d126fed84c09d24da7a4d33d11e75133c3b92b355d0fba7e5591b9992ff3dff715342dfd56866dc873f5ca7f30e0fa6d4e7b127adacc91084450cfa3e80e931dc4b07f1441ade5cb0438f0be7879ebed41f4670d211749a78ef20625722cc8ccc1634de1ffe3b5ce905197b597e59dc3e5272262d210044a42cfb42d50a04ceb1084f37521b9f45cdb380a6d61b22552d80f5a0612f313c6c39fd03ac69fb3ddae119644b3a437a99428a1481ed7df1081de061451e0ec35195c016e7489e556e1567e1fa867de9387e46a1a115e9998c812568a3797013c71be8a452efb12d6eed09c8433cb698543a6bdc8d0e6f5be9f09a06485ac5e60a67b3e3ea743af5748934dab5e881089a4459fcc893b533d6914aa9579a01d60108e7774e4a851c793b3845357f909758ecef9c9860f701839459bdeb5307aee0fefcd4f41bed9242f01fe8e640dce6360156caef476d30bb5e846eabb808deb7ce41df9294ff9e5436c4bc86b927d3ade0a9b7f97805cfa96bb1db8a6f51347228f68a5af78ea0a36036f8c8f905a2194e0a3a5455bc5b5d566a9b5ef3a9cc74e4b6f57d8a99fd8f9b6ab66c9676416e53f20ed58b42c8414c2f5cb70084c3f209d59efc0452c9985da95557b1fb8c164708a32c58b0f6060c92d89c4ceeacd2c17c887e16163272c7e56cdc17f973d3a31b7450b02f39d84c08fb1ef2806a5313db72e0b56bc0851830ef83918aa2f3bdd53d878b55d6945ed48d86beda2f968ea7f003f199411bd2e581dad81485ef3d996708f80e0e4dcbe25d3f6f870bf9d6dead663ebba7e74cf7b88aa4486c1cf4a4bb69c01c32212f5a8a33c5871924b5dada8d5bd14e17be81fc08172c79f84d936b39afd846d042d4d292e0f6a2f505c48969a1336b207a22080f3b16fd0460c311bc56e0f354a89bbfc21296c995f549b51f18f8b69c4442b761493da649f2bb174963c3487e054f67f758c2ac4f210c1140ef89f210f98e67b908176024781ce07a1f38c3fa9bc5ff5c010996852e530cc81a289056161ed20ac6a58b78edd405fc2c1835ffd51d948a902680e1cdb746c175f37e203000aa2c3b013af6f57ac2598c630bd02ecb7e2cefb87f1f6fd8036706c989b330a1093a2ccedc075841331064cff27c2e6ac9b999f6c8dc01f398e64b8a623e94b377b0d58b0570dc0940ee9c9dc283a4e5dc9b874b85daa9ec0fdce25483e2ee8a55964e73f10e799ec22f918e42b20101a5641004365ebdceb57e09b419b8006cc53deeb5d777ffd7b6d70f81eade50debf2acc0264bf0b2b7ad21f31897518281401e495fe297616fe06c26229d3de1e738c933f81936aa5209c1b919e4aa8da0a6a12569b89b3998d35511cb92566759fa87851c2a62f5b04e60ac38298f28e6c69f1c081bf74dc8a2adc7e3f941924e409ea913e6d919aacb8bac6e665f92f76988a661eeaa532ae2ee283a6d9a5e1f529904dca6bee4759fd22360f0c7143a364fa7bb83956bdbcd2d763ab057d6391f3d7052577ef1d59947be5848db096be965257de9b65f949c0b66799d77bb5b9a1e8ff36e4e286cd1defadb241605dc571497c9dd040d49ee1e1c2ceebac526d552364b07c518e8be8276ea13fa8a7bf3b3788d648ae58e1dc7959cca7d89d97cd66b33f746ffc63bd5455b1af00b7c35b802d3d5a90d2ad02c89260943a34d08388ffb8c0db83353579684c804a49b63e42ac14358dcf5433ac3fda6a3789b0f37be8f604358b0780c59ccdeed06e5e6408b641d176e47f2681daa1f1f5affb88f89d5000ddf472484a8a1991a0f9f75d5b80f03f074fe748088c1e9381a2239e1ba0f7b58fee5d9a521595651b22e8a5c9c9f60f19e7657786ea37da54a960ca20a7064be67b03b4fabfdfc6f8c95f9cf356e33e0dce54c0f93d1b1d302a6bb41b6aac2a6016bd07e6e4a22d98e78c94ae60440789b80409e8a4c9a105871f87b6e4256ca18ab988b883e39fa7cfd43e4cbc3e6fd477ea551431f2a6cc2018c207c9c4048301fb14e0657762efb106c41a64f19b70070ffe263acb2fe0186a757a1014f874fa770df731802285d656935e0ca1b7561c142569cde969c6304f3536dfac5c5350f221d7ea3af5d4d90fe30ffacccc1fa8fb10426385c9fd22966ab1283f6f15baa5560f37006166be7e6d42dd128f91a902746df6c4c51d5eaeb58595db3f80616b5ac8f8eb3a541a6ab59434b954959c5a7b0ca8305ff27da6f34c7940608c16b8ab7f1762dc47c7fa4e74f1852e5c5c5a181431bb321ba7321ec34dd6bd9e42e6d86b7066159897e4e6f749ea9aab6b90844a8372be623ef8e341c31115fadc48dca148854b9def0ee77169f0e7f716a735bab0e6dce4257774457358b291f34228302be8d316d3e0c52d5dc888f6fb9597155ec143702ee81fa9696c0e23a62406d6395d6891b1a366d2037cbd790e6ac633b117a6b39644f2d6a7add2730381cc3a75df4ed341f1d31e8a37d2191b04e04c845e9312fb3f258a38817f0ce55bb9ecfaa826f09c8802ad266235a172b9bfe5e8baaa42f2c98b2bdff988cb93e98b6fa1a85801e580530eda716ceddd04f7888130570b1b1c06c70b0cb65facb71995fbbe455665d4ad21fb9ef02bdc2c45366a4e6646ea3eaf792fc0303a77a44f4d150017ea82b1854edf2ebc6d51f03220adfc9bea157498e0f0f9b3bb04838ff8d3c4e7237f1416607e12369e4d2ffd294c02ab4de6ccebd7d5a264af9d821d1d5879fe8e80d60c108c9f7b418ca1452679d8cb37ece22a6d4aeff0cf439d4f69a5b6de191c1aa09ab1de525ab13b8b38f5b787056e4030ad2e96f7996092ab144bfc1ad4295357a8d0e092c9d97051707a124c337e20083762175df4173a0ac6115410815a0a09aa58bdfe45dc7fcc14753103df8fb8981ffc6b87537434c9a139bc24f95499e68ae5f521f340471c9b8b519019e8021b0f902c1f29b7a1c1c459c0fc527eecac565be6453012a767ad77280392774c4aca215fe5c41ab07d83902b2e2d6596d860fb9e915ac4eec50a268b45a9cda0281a161d1b593b9e6b4a1bc709a30329165e59a730cdfc2c974122887611f2907a649bcc3a22affffa4384156430ed16177d4d22fcf4dfdf3f9b99681beb6d25de6e031ccb95cb097790d8f650cad3f40fa94520f5266b6ae3c83f9ec16dc516a3cc9c86a1e869e7b421d1a75a12376eb784e6535b9472feff13b3872dff6a4d336635823e55ce4942eb5dc705d3f540f03a0f754f42c31f088d778d2c1d4d5c7efcf412ef2ad4d9eae21b37a78da96dc043b9829067a0df041fdfd74b2aade90ffc985c1ad4bef01a30a3275de4f79bb264d06d7d70a386cb4561d877ea395511b4ab0916ce0286b98564114aaedfe56a894b7ed07c756ab85e0c130f391cbee4c99c4ff41554c2f73990a2c33a151d66f593b775975d6ee482caca147fdc6855d0a9e14f2a467d737dee7d794038764f7539f818847b443b67b35fa14d0d4bb660c1e708d1baaddae3e283c4a4b9f48c4fc1fc4b7913ebeb3fd74dc0aa00aec613558b8b7a555acb5406a7b4cb762b68748f2a25dbbf80283c331afdf8966c78c71a0a90f1d6ecdbad188d46bd6f7f506655496fae5329c577c87e630a31cef10ae33cdb3fcd3a10f2e05bdb94b52932e249c84b1bbff764789735fe8d2acdcebac896686d952a78f015bcff634fa34edf72e3c4bb6ef6b4873a0544f9b4029eebdbdd4e393e097e83ae0665e830de9a4fd705bdc21474890c612016a72f9880dac579674d6e390f399da2de42425ea3281ca47831377da9a932cf775825eff0700f9c749bab36d03158aeb78c1a0712612660191922941d5a9db282e5b1c3e46087f7a40ca935e1a8062bcd91694add4ee52335b8024fb6b613d6f35d7bd92efef22bba0572ce00daf4a44e6a05e020f5a52da93bfc7d4a9df2d005c06977ca51893b6b8825a35b1572ba671f6c947426999623b169783c8aadbd7b4299c2afce347b266c3cefd6e15e872834c548b8b155cf75165923791ea29d23b484c92f02107d225a4ac042345f98f6b1a1065d410a9c99cf2102e77d3638e2d29a0fa4446030643ad524a449a0e93ec7e4d3dee1217eb3895b0cf524319535c22583a0ed5a5c218bb9f3ff4b54987f5d8b05fa1108b33e7dbe7078570bb99b3eaf80839532337b77429be571965da32efb7f6bc9543e58d22c9955c16fc460a81641bd2832d4bb189ac19ae49ca49a9a038ef85dee55dcaa63a8448f4a91e35956f200e2f1c2828ff0cf1a85ecbe2ddf657c723b392aed9238d69910ff303d8ac653e4253c5e14bac72c78f8ec2a19c3fa69ff7af03ac23bb5e5ede7061511801146d86f4648a1190d2eb92b702dbfe78235df5e32115369408c2dd8041677308bb9c13fce09e85eaffbe922bbb94fd30c216c840f0e083f937c02447f10737b7dc5598cbbf317fc4188a3ebb797001186fab2549f23f1bcd54e9a567dadd27635b5343e8858b6d4fe067c34b15aed560eff9a33224945ffed841c259b2a34170d1570060a30c494bda2317bf1fb38c71fb0b230166fcbdc35c352662646eecfefc8c46696ec3d92380ff9100455f1d85bfbe40d5f8d1c82ca913a866a2d597b9f43fc1c5ad032cd5b3b3fdf5d8dfccfe261eaf56afe93fe41a5d1101bdcab687fc69fdbeb5827cfa6cc8b5ea6e0ad4b37bad7ca39120574f30d945225cc8dc90f55d59fb9fe1a5dda7c2281e9017268ecbd5703e562125263bb037b40e8af4445a844f119422dcd5a6300dd5075c14a7f9b4a21fadb59292d332a77a7198c9ca5bd2b10c2e406310bc025f07693460d9a8bd24159faf1596bf9ec3d0ab1f71811673827285411fbe882e15f7abfc8025564254e751bb1486b65d5d18958d81cf7668babfaee82149dfa1d9f89f69313d7c3fd96bd897079319dd6be43047b49bb9ba3f4f17b7b1f03d14628ceb52f3b62ea283e9faa8d19dcdc1823faab2bea8b63a75ece0dfc22be0cd383e9564164bc10c2facd7ed1f999d0c6c8d07723c19244ed3576f3cc230574d360e061ff205ef3032717ee54a018e05de7d71fde45b04fff4e7cb896cde73391acfe30e7c12c49215af6befa3ef17e39fcdcbe67a8176cd3715e6b54557235395eed9eb067a16a1739a3270052680d7790b755278e7b653241d10a40f26d52a5cd4d3f0c7c370c98aead80630cb8ad1164fafccb033d8c1732b388a8ed553d2c5a0d7b370b191dcbc082a28908f68a0b695efaeaa25118e36eb94e3a1b52f8194694e9ff5500f57abe5e3db02cc3d566183da973bcec5c7ca748a71acc047297ac875539dd76e2d14486ff284d4332dcebdbac4e71c225ca9b38b1dc98d3611b50b661760df2707f041f9d7c396f8a3d4bd76d19c2f6f80abf1739cc6351eeb8cc9eea9bb5762390d72483e125bc9e61db40221a49a0030a871061c2934b19b40fed0913325f3ce8043a7042fe0c3d588924eab037629b4046cd17e48184fefb8192fa795962e1467def0c1b8ac7d90e6e0552393affbdcb500b22d4e36c922c2edb87f7389d0523d8ce6a834258137aac6837108ee74232a37e53bd54a155459fa77f28169913a2daf0ff1ec892f370312d7061d3b37ddb189217d66e7b2f17e856e39091208d7cfb480f8e922228c793399f9c276f058d3243c9d38b530100b4a1498ab9e41267705856346d912936ca4568fddd3ccc38caef27e757e29b648819d2e4c3b2695b98ecac866b944ed4cf77661b2e53b4b76593a86df4928ac88c32e8acad09de2f24d114199dd14e900b2d5343c784083dcccba75dbf445280643bb3ca7bcce6eeb4650c6b711a5fa85ceee73ee78fbe13cb763a5c04069dbc3dd4a7f1629d428f8c852a19ee4605c230afe58cca7ab289e1df9ddb354e8f7c63b654a882dd178bbe591051829cc176ce7cf51e8b5fb61b6cbd662f705db4d6758b33c4702dd617b7301e37e9a04efbe208fb9e3054fa9d5ea1d07c0e7158f7a13c03dc694119dc82b438d3be7d4539a0ed268532ee21c54746aca45766a96a4136e419bafeb8aa981bd424642eafa24c13b4734c68134f2ca55e629ac1a069ce42e6bd3882057efca71ce66aebbb089794f61a3a8d13f3a21f9b95316b1df648134504aeb61e0597af2db95e247734ab373ce48b649ab9bbfa258c4c1f5618ad0b4dfd1d09b9e08f66dd6ccd3cb215a1cdd4c8f7e43dea552e059104c513ba40119945f7d650c32e71894b39cd5c146dd7ce2a73c3a0de6f3df918204a15b3c5cc6ffe0cd4e1241edd12647a7d51a296a62c87b86ab0f30f6268354cd2b3655a3ec049122e9b6784b0f2bc07c09de3e054789c82f7a7585e191e7b0825e62851f579a218c26a345f2f02345a225c446d97e9a92653b1e7e644cf4a07b5ea7e888642644a92774a8e17471883a69269ab0940f6ce267aa9fbf6e100500afd027d30d3af4f4f99776a39be3baf2b30b21550676f7f8333005cd97541d3701d341a29eed9107dc35cc864ee0482480e0c863b31ac0ab98d51dcbbca2b33f397f962448603401966a29a46ef35843242c167ab6ebb2fa4eb73a21760bfc99986b8a822dd6cec81b4a99562d0140893f370ed0f3c2adefe0980e8195e718c11f47ef72f8131d111b902e93a4036fe9ef1c2e169234afddd77095da6c3065465c448f0b2d3aa71b557b66126a2f95a030336417fe6eeb930cf2632904ca53074499717c63fabba5df2824b1b7c4801584254dbfcdf2ef7f714f1cd2c4c4794590965602ac47a3a8007fe121a306f22b5f47ded2a1332f65f70e05cb0ce8caebc0eec229d5b8b06cb9b569c8eec4039cc692f767891c20c338f42e97fc1a7e8bff8bfcde1bee64c32e21d6b5f8bf1a15b8229d340880eacd758dd84183eac2d291a9dcf18fb140c5a870cc27d8e7a164be6d5d9a56312359c1946a55624b02131159f70e3770beebaa213f66b4caa0598bf7706a9ae843347d9913166cf4178690a1049e9a61ced93d803ef7effbed3b109ee8021c7e4fbe925f532b71d7e3d740455c2ca7e459d70d219a902d223f610366d617478ccd34eedd39a8470afdb95ee5df6c61c1093958f928b14b58fcba75da244be54ef993b01d45c2e2c8289af1d8503f55d65cea878ecf62ccbfcdb375a7e5b741f07facbb915fe1edb2131aaa7b7e76328d509d90750a5e5cfbc78c662dad13decdac7851ddfef4772e4713771055ff3f588d9e45f13bb0b0092030430b797be39034181cf345521efc74090cd64a1e0ef110fa5b62863b0c1d901f28d3adfac3e57b59b4a1fb0445baac137cb4e0dd0e2ff1b17f54f7bb640b5dca563d960e381e3646f3e40c508a4ca9ed280a92ae62b27ab8e97a5d62ed0203db8926265507d229d328f362c2dcbe374888861b86a816f92b901abcd19728ecb72fa5f79e14ca00246a85dc4f05981302033a50bc26f547e59d09e6a29cef00fa29ea6c3b5176c94807f435c843f177ff2795e58817ed92047f778526054cf941a77cc338792a116a02686553210765a8ed541be5c154cf4c3580c2c5c6f2c52cffa8c45208011db54fd03ab4116accfaca3ef164b98d1fa9a450c626c07667469504f9a3664bde85a1bac0dc94d4fc95cd9fdb098599a097af31e85d7acc5a473704edde2393314354894e1e3d8b1828667bd0f4f4d8298e61ca2bfb7476a0f24e29232f2797e10d83dcdbdf56a5e421187d914a190cf5f4fdce67745fba1383c2a66a29f45b69790c1dbd0b7045081532940d9431a78de6391f522bd0ecb7cc0c01bf7e818293c3513ed4f9cebf2cf19f8410434200fb41386934a1f943e65cd27c1bc7e5e12976ed5a11dcbed0b6f485c7ee10e22d803190fc537593cbf01d63b52091f55ca13854bfbaf27cb8b316281b19a6dd1ecba757fe5a8ee3dc631e1d75b8a8784df01d663418095405aeb8a8510d555e222a4e5acc6904b3228b177ce0c93080b2fb7db1952efc6c618dfa5e2b492d870aca45363cf41f8af6ba44f6efb1f8af511578057bbfda8a8a4ee14a0ff3aa4b7985acab91840f5d0e90eb5a7be60f722c1b62e589493a8b2b267592a41a0b1a49b14194648b7414654802932742c5db0e90a8909b0ce82b322a16c5d764b7dd050cf91dfcc33999e17a56897df228fdbd9a2fceac942510c2688ad47ed1d86f237d7af51fb89bdff3c5f79d09008df29559380f5d748ddd837fea0a686495d957faaedba82f71a14b387bd322882b1cd016226a2f21419488ad15fd2be7628da12d0710a037f2c3f63a137f1a66cb4a4bb9cfd82560c25c38b1c0d2e3c015d60b64bc104df696fd007dd920b2a36ad321e2aa067f0d1bb480a2067a6a22254df2ebad7451de8bbafa028bcad6ee3579b3e3baac8ef0b48eccb85e945f80ac84690cd22a83583f21d1308624638ddd67b4c7f81a203cb486b1dc256017d79756b868be6dcf10c27b7c99b43b559545a07ce1fad5385ee049968ab4291e6bd8d4e071b9eeeb82218a4cfae60b1e8a1c9b4fb61d90b987b693ca6c4951930ca40e947c992640d2e6b00bafb2f570d3a036ef301c8671d0310db7d745ce6cc36b309c7fb65298245961a6a35be3612bf341fc8b83e499b4d1780375c407889a7888baeea4407f7e303289ecdd2d119c9d67454ebd313ae6b6c667a0e47927ef05eac0a7622a15005f27a0eb152ad67f30150ec72c96e05681554e1b09b7b7d7be08db7f066472b749c71583cabe9ef6033ecb331968b290db368605a6cc191591955e82461ff560b73fc8c08d3365f7a5b56c7d853a9dccd7315c4a821383f6364c92682489a2160088b694506cf4b052e4c7ea6271345a48b228cb13a34eda4fa421c8805cb04ec09ee933aaf75f6e35b8abcd629746de287e6af401a4a0fea97de25b3d90452053169e5d55da34c2b6caea92aca5324a620aa03b2e9d4080537261f46dcf1472023c6727f2328b45ee3c74e92103ff8197f6bfe756ece9705e98e887f7a9bd69cf8c27151066ff90a08ebd26f8727ea3f21863cda024d85fe22a287862258a853d7e487f9c46594b2baa0f4f044c9bdb34321f1ae12db2bc82023aec1fd815233ae403be73be6e188d292bd4b56b0b93ab2563e664e4b9657b10eb1ef4bbb67100b61c283767c64aa769cc69ecd2a920c2182f134c2c7c4b5310729bb752d93e51d36ae51b8aeccc3ecde0b77bbaa0351fd27873146fad336f8a0d81fd05fe532bf4876a73d6ad049fff5f6176479b5bdd16b6eb7a209235d0ba950fe790df279b254af7c3517965603f32ed003865bdf6f0e5306591055b41094cb794f2f4b48de86a09df6ff53f08c0c41391a6c4be92e4eeb3da4805661148d555f32d80d58a352df2d2689bf711fc195e6be8356b88583855059f6816498b389de99914a48eb4123fd51d3cf6a303fa5d6384007187dbec379a3728587212d6f06c49139eedb2ffe1ef3395e52335e43e01783a8b0f2f03e69939642f1e145e285260bae54a631be174affdfe04951bbaadbfc381a396bfdd77ce6c5dcdac7d6a8aa502d20e9c25c4db7375d7d60b3771d9c4c1144bc35f41623585c43806a5d5a7be6935281402a01b6ea719872d2f2dae26882017da322c4aef525de1a1a63286adb641d7c374132753c6c6a5c6927a4a127caa9bc86dc5f3314439cb85a24563707395994923baed86530313ab146161c4ebcbf9b8ae667c7820439280dcc50d61d7b1e45b5dc48c3085feb12dae274dc11c8ebaa2a978e3e77b06dbf6b4f1b2be10b9591c17ac3e5bef1b453e8b126f81d5ff3bb32ddbc7a267fc4d6578df3ff85ee5e332da9b96fa4650f188855642b472c723d71de20fc5b73001db0f8ed54c56fc456e220c587da2ccf73ef6d059cfe133a33cb0921c45d62305d278d4838095a5e3a493948fdef231ebab56ec5d0dc3cb2bd7d3a2af343316b672fd44cc940361c541932449700057dccd782333f60ecc8ba3a0e4225f5bcc87c5be715c20c068ae80da92749aa8b8a46a3c686f1617f7db50124ceb435ee050462c83010f151ea60be3abb4a1a1ef9c57ba3dfd36805f4f95e15226ec31a0cec9fc5f597b9f2ae6ac180ab0e7f45cdac57eacd1e33029142fa850397bae45fdafe4001cfc262600dbc6f390a4e22be68f04f93b167cb89d5d093e21fd52034ba4e4c00dc29c568c5d8d90c3442e229244c9c2dfae9ed8d3554d718375d593de863c31937632fbafba61a82e1bd164521ce65d3fde6d9746cc13bbc7a0204b53a4172fe742f309e2460952c2457ccecfb0e7be0ec836a23b84a12d4a659614ce6b37568880ffb5638bf5ebd62cbeee1f70d9f3ddda250fec2f2767e2975f848a385c1292f579b2ba51152a41c2cc3cec786f5d91cbeb5a86d9bb5d60fcc1d3a73c641ff30d3db1385b4ea8504eb58aa913a6550762e36ae792d1d280addea167c6408a23cc71710a043a55f997a4e5bc45d4bd908a975a6a07f18bc6ba5f09f160f870d94318c66fdf0b3afa4d2fe53cfae96bdf8e6c9f943005eb2b7886e3ac25be4c65577887405cfd1ef89be70cd25d86062a1f9c9ac42880f964fc09f8dba2c0f06982db8aeea1251388dbde8a06f370f9cf6c63e473e319d649989d46ca28f8ca545e625ab715960d7ee1761f8b9e5ec7545f9140a1ffd200288b39281492615c5fc89c4b49dc33b8ec7419a2d004df3777446d008fc239d785b9d4699c1b3a1e6bf731f82e40c903e3aefe0a5ee32c60cfcb9c3fe70da8de0c57190663143f2018b36f591ba0885835fb70cba7bf9e90a5a8af8ff9e0dd0055906f76405097f712a085f3fe5ea9a633263933b6e73dd1baf6d94bac543d48745c0b39b96ac05f05e5a4e4b823a86c130cc8e93b2ed0e916a31ae02068bb6767bf6f65494a673eba8fc59f2031a843659b0b073a75f2b54700c4783b797cb1912c740408a4fe81d514ec0541a31275d76a7b2dd8678190752d31893f8078fbf0d8f365462997dd43847e4ce423e6a2d496e98e230880c522f0b77294ff6d837be73e53458ddf5fa48d9a78a5fa7d4a8a5bd224457de3dfcef64f1cdd479785cb333dbf972e2e746fab99146d1a23314b297094bcf36316195b8c8b608bd7a4f417f1ea4e13361bf6ca0ff8b6a258713cbe79894877643e367137f04d1f9cd184a66d137b13be51fe5eb5361ac679876880f06ce29b72d3e67e5fa2d9fbaab41194b52b8faff14713119ddb2d9d6723145447d7c9232e99e6e7faee26ed6b593e2985b5d8ed0efc68cad9daa86ddc67a3283235d8de5df85930fb6e573a336a4a9f095ecd46a495ffb60ca4735b0f6ad39f0b375d92447521329f8b93dac02c50db4595020296c030f8ea24d29fb976efbf786927aa83de38ef7365c0162cd7e1092dd178470d72e4fad987fa4662cdc588508f1055520dc37fff31011615dfb0d7c15c727a9dc28de3f28b7df7476187e01e677e422c2364aeb7b0c679aea74a4fa88b61a3bdfc498bde7a48fd7fcf265e7ea25e12fbe1fb37f47ea2ef10a6b172cfbd21700cbc2334b4d508bbfa69659abc778d63798dcd3fc9d1f0087168c9503f8f1dd9b174a2703d1f9711ff214be82cbd2e61ddde44c62f2dbfc192a6a237647eb29f1e336c653c3ab5f873d22f8a92668464e63aef9c3edeea74d4591977d9f70c84f5518f959ecf993e15efcf5e0fe1a0ce06dd25077220e09ebf3013e685696140756049af5563c7120394b4f6008efe2cfe46863cf811f4c3a7b0f3f1c917fa032655358228f0ae3f7a25b6e39fa7c65e8447f735a69f066e47036dd412811565485b762258f27191efba2f929885fba42d2a7b6f1fd569a5927806ff2cd4632d5cc2fa2215e64bf3a231b902ca64fe974b176451414c1ec37e67102b8b24609e5d709f5f8adff79d74d0285550b7a2651743f8e8bb51d0719f7a3a8e466eec2dca22ada00679052386edb60c04d19a198ac829e69011135d86ab8de4f02db3104ed8027372894ee5c0b04a4910c0f7f276948f58088b2f9475d0f0bdb0fe708e0a8b463a945b641b3bc505b41a1a8405082e3bbe9c2190326ed3814e9384ff0866ab92a9a09149e66c07eb80edd2ba1093fa13b18a6aa2e3a6fc5d296372d4b321586bf58a2810c4929222769502b2babb9f9d2bb30476d78b9ef9c3e12745f0f50ea41b85ce921699df75b92e1e70ee16b218dd1f60ac0799d7481b8e83826a351e730b9541bf6fabcf6f0cd3ec78b16a5770980a135f17c630df26f3e1ba107bb091e571d99574f232ae415abbab2cefc480d56b5a9f1788ba832d6218736bbe53b5155ae8ca69a8aacd979f9de0dc17ec3992e8eb05531bd63a990cac0b31f97f32fad0f491e37ba72f2e76cbcf8b0946cee0c2cfa2c597f365c70456991f8169a15db06c865b4fb792efbba3783ad061667db3f36d82008a2b0e28812a9694822a68c6e979b638db22c92378667163a447579f90b236e4cbd66351bd6ce843e5596ead2691ff08e8f5c61b00943b488b9f4a6f4a4061ba5d0454ee8889a4b050c50bfcb3ebeeb368831422570be0dc04862d0c28cc90a83a3604f19aa67f0f594d665888d1820607da3ce21a262f94b43d0aca603048d74e973b29874a7393965116713103e24faa9389d446386d6d02e551fd23e71eb136a9a1fff243a3d5ae841eaf3b743f6060e0dfbd860f79e3f62c7ea3423b9e00b41ed1c86b5daa18c6553206e8db74a8873759e0521928f1b1cf5d40bd5c6f5b574bb70ccbb6e3474aff1f4ec5e4427ca5f4485523eba9437b287c905b937975e69d3babc89a0d65735f24066b3860c665ed8820ec31e8071eac6d8f2231d7c4f1c5c8e7106b09d59e4854b37a8770857636bd4b57bbee649c9b8257f4cc0d7c4ee28073201e774ff5d6f057d5687f9f0157a701d00b430cf9084cd3a86fb5b34e7f27281cbd883585428d6122588826819d2bc54fb537683a2bc936c945d2982bfe8ed68f7a5c45faa2b25da5a790e90594f6601bf94818162cde2526bebd2c8507a588840d311521fd59811874dcf03b684a35d4bc3e36c0595738bdb88f1d3a88d2cda48df391bf89b3640a581362bd7c54d5a16d913ae5c132df6fa34a97c37f880a6ad48841a1e347e0902caab2e832affcdf4b8a57b6ba4f337edaa3cda679c5979cd3b66843708506a4c0194e3666d15fa948cb43302670a7b2d756a1e1d09c62303ee549371dc2261ffd45a94cf7deccb8b381e2df934d3eae9a4f6c7f5e2624f8578682a75bcd25d01ac45314615c32edbc15ee2ea34c43f1898a438798299d1dfd40640a81b8cf73bdfdd89124b801e77c9e9c2b6b2f0b6b6e0a00065592f38c156ef4ae9f81a693418c424f516f6b3f50b9037ab7b356f6d068190c04b6445ea6001fbbdf94d0fe607e947d2e9aeb251c22e469d1e2aed89b9dd9ec1ce0adb8f8a32aed17932474cb001e0ad20a9444df77e52134100eb38f7c50212e8766a66dc55a5918bbabc4d6321781f23964c3b2fbfe59b86ca466fa997003831a3cdd3abff06b7b6a51f3d2aa61627636ba5b1750027845adfedec913c4b8fc57ae18a626896a3bcb1c6be895a3ced18549b24fa145e3595f9a2797d22a0b7cefa826ad0ee5d67db90e5d5060fa67e2033f89408f294855483c81a23f5b810c77bf554fb7a55a95f71b51c053e09bc1b4aabe25f3c8665dc0e64eaefa56e9b3402727a5384403ee6ae06242a20809efc4b88b628b4df81d1b0ea4a7f08daf8ad91e2ca43daffc3c8eee81b2193edacc4032cd8d1c4cc7123673dd39aaad3cef0e5e17fed17ba2719f815d650a5fefd4fa2f5af83b050e703fddef5bb6e49a295cf813fd6aa496d08e7f680447065a208f36a2ebb9960924a97dd0a54d553afa70618a3d0803982e8e18a6f71b46a8ab491d78cc409f5c9ac2fb0d11486aa3806791021b73cd508baf85e91e245dcd1529cf1b1376d5e83ad75f23776e8bb8d8c29473ebb98842ac8bff2bcf1631b316b6e292a15ede352f73d0680ba9f35294c94d47401e02559951e4087e3b221f722f218f7ebe1f17bcd655d2bb997b2e2bdc7cd892979a69bc852c4552a753b9a45216cf9031fe6e208dd6d0b560156bf9801132625a59e198d4ee74a793c62fd471f007c31744353d224b0771272346359e6ca39bdeffebc1b4b563d881e59580b9cbd689a5afc3d53314dafde2c7c651f8701471e45213515d36a66de4d020ad00d3357cac4035cb43c6c83b478a7c9e472c7789099f6aeb1884815da1ecbd64b7cd05cfd0f748c591043c93f248bd99103abc01a218cda0f4dcca2c8419fce0d4dd1d8d66a6fbccef624c40d5a14408ff55edf306edd6b9c162e954841450699e72a33b0b96bf11383f24cba9e6dfa1c95d2a1dc9231d4b3766ba43cba833652dc473154b1cde9983ccb0898ce44fd762bf7d2dca485e4e234cba99d74ae31506979343a95fbddbc947f714a876855fdad935ebec703615bbcdd5a9fbf1d495eb6df76c1704b87734f359dd3d6697e74f79f4978873883dcee9512dc322a2e01d5a0a0d34eac26de1270b75e787240af4678f2459b18988823aeeed588aba11b047d1fee1fae86722fddfe043dcbe4330ecd730acbfd89ba51c9967c96f1a504af5f1d3135e15eda63d3262d0cc36bb5818013aa268ccb2aed7a14dc7c50e67c7ae4c4c9569736d03d18f89afca2c4937caf6db5df7c15ed4ab5221c2337d525a966f4686e368629b72f252ebdf76cd8aaaa6b5e01e728b2e09bc2bd76ce5c6f42f57a813ceb91fa07427b021388b574cf764c7d69a6d27274d1558f6fcbd5cd76e2468068ff1fc74a2d5336533db4944fbb4935ab2948d48e459c5a0c8db5d150d8aa2a43fefc818e3ea1e136b3313167891116effe5b11eb3599a1159848f7f8c80d5645d1842430321b10781ee4301436ccac543e1e099f33f1c26ae3870c9324efd9d1c114a0151e33edfffd565cdc4569a4ced21d31c48d19897c40057ffec27da78a250209ec372383ccd2f0a2607e05d99a246fa5ff526caa4474db357768efa28b3732541d92dd052b52334fb3be95df7234c55eac90d15ab1645967cc9270f9745adbcf809cf40530d10e78048bc1aa58351bf4d43a12050edfde9813cd4fa0556d77e2791303203e95743d5b411ef1d8b6614e6a40694c5ac39cf8b2fe3bd8f830ee890e687d181bbdaed14e04c0bc1ec769f7ca3b3bdfacf2b240eec24a98f70644faedfc01efb73df262035845740bcce6770b80c1f0a19652bc1a735a5cc987b473e95596863567717eb8ada933df685108e4ee005b71e39a7d306475931ddd135909f5f844b24e4b99f6be707b634f6138e9f75ee122fb80dfbaa7103d0d45cbf67746f3e268844f4eb9730baa8ec2c70d9d546c90f85c3217dcaacd3fd65bc20af2de211b17d22c11653759b4cae09c11fe3aa5b1c774d881223f4de1ac82c219bcbc41e5364bb5bc34dbf9cd74637d01537b2897bb7a7d33e7aa981a5475132172cf52e2f3d5e6c96a02607200af5adb713f397200e4749efd87c4bb3088868271153b673dc5f7c626ef0f32838b7ab671d216605fe0f554a45239aab9aac3135745f93fc9f640cb76254e8088c206068ce5c173a85c12167e1eec8ceb73a5a97f032d36b9042172e9ad717f8375e4ea1c307ac5e9786a9c7cf4d4d6912078665aac3225117226ef37e8260baeb4f3e9ecbfe9eac17c638db00bd4767949a773b766aebb3dcabd40a91541445f0cc83018cfb91248ddeb86d710120a38786945408c0fdf6fb4919de7d165ffddecf6fa9dfc887af494bdf83d7aecdf5f5b2bc03a934c38822ca8871316c1e779195bc6037f004195568d5b9968bfa60627e783fa9bf26bdeea9b0e02c010e28b23940ec2dcfa21ea433caefa0776f86c7158e288603d1442b010a7ca971ca011d16a98a8e6ca48e8b0a8366af6c6718277dfc0f0699368b762aa15cb26d7115db1571bd489a2369309cab4a66986934f761d1c03f2a6a2f384d3cca119df659215458186289493911b314e88a32a30f3f1d7944b1ac93114f579e855ea24b14ee302973e8b1040bd0f00ca5c2da54a771ae7213d2a7618629927746b947e757869f67830375116b02e1199ef6b1f5cf3181d7b053e34613f2354f29cfd70640a4b193ac915498ac0745763764b36cd0e8a366991249ecd37c53cf9d780192bb6e24285084dbc468e08d8a715351342cc5ded336620f68203fa9265e6eaf5f1fbb0a444fbe4c55aa48dce3c6398cca66a7acb44053eaed01b4346ec2b8646c3edad40116cbae97e6ca188e002c28486998f397adf946225d70f5112d4eb3e3f7479c2b1010ee4bc89eabb5f321015978f41695b5a1f2102a9a22291c07cb20e9eb95738bb5afe9f215f62c40c5620ada747661ad0a3cac634b18222f7367914176cd5f94706a3f383acb711df28808b3e2cb525280088e118169f681008542a47e3ebbd243018cfc513e40cb43c6cc27af1e2802297a7247c584977bbf72392bf06d27519edf3c2f6b9b9235f07d49685cac0753198dd0240f547865df1c7215589fcb4cda0f1a2b4e1d1d95460d19cc4c2b8f68edce80a166a8749db712170fcafca9ee516bb72e90689fd1da318a77c51af142fc5d2e3eb3668beae4412bcd4a43024244d8314496217ecbef0ed6e12556e39eed5a4cf5df364b3fcafc2fddbb634ca83f5ba28c87e9c02dbdf94564a4696864162329767157e7ec23c8fefe014821f8c512c397816b0aa962cfe19766d0cc742e6d883ef64ce3b6a4ac80549d4cfc33dea76a5f654682ba56f509956f9dd2adeb38c9d9492f9003aab33c76c8e8c18117ff60b2b5a85ec62307ba2df29f0065d2001700dccc53315ed6b8621558d6d79f3451397d77d0e1c28980ff326b39c6382457f49513775c716c81650d3ee5b4919f3f9a0e0f4421ed11e5f19a18af260f5afc9ab92899f80793079ddf36a57c24b1abfcd7c42bb4167b98c8cef1176315976da2d79f98a08ea8cb6de42a53f830e48a81f67a111e5896448f832530df10eedcb2f17842d2934d402a39774209ecf77aa537c3114e6f2e4fd3d823af5e36f94200b0441db17f64626f8f2c9c18b562af1da75a604b7afce839ebde1cc03a72950f90d24f3c81fc77002dc11ee83b9b4187da803034ce0c41f2ffdb954dde399aeb43d24d40877bc785c1dbbaaa0ce47f4d26bd830ec723cb5b3a4ceac81d7a2f2cf2396ff3fd29cf4c2bc118165670db9a13f7f743aca89180536231be8dbcbcf046f17711209ea3ce7d7ae8ddeaadc0f03be3071dfba9c0b45f36f7c56c909b6c2655c2512ccba3f6601be34eef43d26f93c713479d05288a6e65e818b759e427e1cf15aa6100df6333791c5b56956e4a260692ab601292965792c4783175d49596f79a4f332a342136da127ecfa4a5aa5ed77844b7f50cb5a8a3c48563bca8b1fd4cc6ab7f6382da4a6dd2d222cf9a0733f5db1a5df73b86efd9a358539bbf9f00bde84c9c06c6ac87a33f41f0a7a2f6f1508a6c09aebe5fade04da7e24461e35f19413d4c9f9846307fef453d9039f8cc860f8565924d235e88d95ebd80073e8c1c686bacd3d9067f008ec234e4e6a2d051117bf21122f964eac477bc6cc97c058bcff49348610624ae596ef754646e34c73467568c31b2cfc2e1f9c07aba0d3329ac49c0ce72d7b089e84cfe58a72b54d4276ebaf362655ee57a0b574c5db004cacf3e7d146eb9b8d6dabca245989e8ec8f65cd0da734108fdbdda6c250a4fb5dd66d6898a5483bd93b1ef12e7377a531f41f1f76a7cb02bfc86711ade2839e197614612cf46b3c5277780feecfa719f4c8c350a71702c6c158cabcc264605cfac5337ee01d611f5900b5c6b0be87e07326e83ed01bd6ec35731bfad5ef118eba915b0a0831d3849677a6a0f4cd1cdc7c97f8bedabd465c3b46abb81e57f45afc48ea547a19ab6c8d96da13fb7faa4f1c34a96ab0f24058637a010fbb1e026cf6aca70c6783d70c919f3b3e647ba8f5579ccdac3192b6ea05a5d69b80d9f33a58c4af915c14dc66e9e246a8b464cd74276fd6dd757f4021be32fcd758ec6759e3b568817a3cd2b645e0d571d8c82cfe46843bb3da60472b877734252a4dcb3fd39c95cd9c788068c595e1d6d3d9d4bd363bab7d8d83659ed8e12883725ce5bac0c3cb38aedcac98568e1bf233abdeb444db93db9cf914f9dc09af4bbd8bd5cee841e9e41853f402f939d180c89458f57bb9a33e5ffd7095aae01c1fa748cb06b6fc02a3d180d9c32736d96d6304a2d256c73e0865c0a890b4ddfdcd45b8e1a8b0f4b187ea4410ef15d61d9f99bf1183248d4aeba52792ef2e008ff6642acf804afe4af43a3fe9838901e41b96eb2c90ce2a77b91cf75ee784aeadd5e32c8a106619f777c5a37ba770135b064ae7a295f92f47879bb1d2a36d1313b14b6af823db91574335b3ee1c12e235ac65a78a2cdee0829166466aa8ae746ba4baa8a00a8270ca91121d3838d497e0aec247972fa9ca85131f5348ba44857c2c05b439ef713da22e637953ed8bfc2b168dc83ebf56833ec9584eaa8ee0328c4e259a5b1844c1f67c011297dab2881f9682c323b08e6e54e2f77862f02dd1a91d80ccdf08c26baa5d238c9d591cf6b8186156c9da2427a3d4f76098640198e93e8fd6e95cf7172405e557d34c22eb292046721758b7765a547dbb56f48532c54f3defc2845d4828fe8790c65e3f3d2ea4c51ee488fc0d0a4c2cec3804b04ed2175be37802b4bd1ca5c35365f26a970d5b8b2f7de40a7d8106dc0e5abe0beaf493d93ce5a58c3e0dc827e25d03455adebe2df4f40a85e2def97a0c3361e2519e7f2a6e16880e8f1d00c7213dec7dbbae7bcb532ac60bf37ed0985ee3962f91cc5d99b072bd44bc8a3e35f1d111d74a899a1c01294f8c9f1f33a391b3424570c8322472fc672ae59ef2dafcbca4d7de07eef491cd656848e81413fcbd0c9360da0ec7b1b27b3fb59286af61a4af65072e806f9d6f1fc8053580ca6e68e0eb4562d85da17043bc30e3b8d0701df8d1ce72ba4ef385af3a5c646ebfe87f062d1e25afc07bac4765e7e7ecc008514f35287ce2e2230047ac70d09c760d295898ffaecc5b2b06282c95feb66c2ca071c796c4009a10ee6351cd5a30184b0f35a656defb54e86a68fc1d8b26dc2e25ab90459d7ec7c01e08570cfaefc62049c5af3e3a79a8d25cc79d564736d89623beaf45ee779502b4e03f8328f35450f5ca7ec228f1c89560e75b1068cbed968dc5804f472805db624d2800c027382bd8e80ec29ecf65701a79ddc1f0c4813f3d515783c895af396a652588103fcb789a0788d19d0b5ef90f3c991878d2bc650376926ae51e4440a13ddceeaf148b6cbe0d2fc6a337f1d0eb96ebeb7571a3511f1432ab498a0d8d58e7d00c83be484f58c817b249c78a78cb27cd5bbd17e2afb133f9c5fb85b4c7b42c0acb08f97035cb771937c625fd2b269f3aeecacd2939580e2ff0e3c39ba51789746d44831011f8db2cb2c6441d3064fe54ad6829f3a8a14b0e6c4a3651907ffa79153093b1e60a246a0f748b1356a63179bb98e530bc89e565eec53e3ed8136fc040559bc70c6d7e2d781e830d6affd7caff1ff73b7aa7d04bdbd38a0d74f98eaa2c4323d5783b63dbfd26201047ce1430efb8005cc6845f96da5a5db428aa5e8459083d250095febfb9cf9b5da4ae8450158669dbcc8aefc2f495b33d14c23bac29f29a9cfa00c82e0c08e4b55e5a8426d2cc337c017e0a5a5d104bdd0272542f5d4ed05f4336ff1c6da704f9990f3560a2a781667af2fb74ed094d77335326524e7e2c5904b5448411df0b4782e5689d9d0ef8be4eb195e1fb5168af9404ee71a8b5c6578bb72e17aa6deea0185072b639a2916c1bbc3e28916bcf269595b2ac58220b36c3dbdf5adc9ccb219d68fb734d5de2d2c02f080d5b07137587423526e20dc785ee7bf03291a491c050f400fb44f2b6b16c6137a1e21f4937775685e32308711855fa1d4b2984a850e40e9af39c2c708e6ac6705d7527352a125570d2ad6e381198a8391fdf98127a50094b77de29eeedbc8fe774bda5c04eff192f78a90335b2eb130877e36cd42487007f17d45194035ac31253f95ec49d3487677eda63c90305d327438ed699bf05746293b9a08676110d9e9f47a860b70d43540d2e214f1cd96de2cf844bba57a50555ec081ca92bb4505b350efdb37c9a9b18bbf06348264d330cc3bec1b1aeef91b28d987b25cdded8f853478da0191d29f0a44bba3995d2c83f7362bfdc86f4bb2d3ad8d490260c7cc3cc2e8cb35a6cf1c65db2ad3005704f2a74c0da5cf6ac9c19d4d97785bc83d29f9133a8e80ddbfc5e855cd184abc9dc557a738bc89606109951e58f1d5eb44c7dd377b7d98778af414e67c67b2459adf7723cf730d7f9895e17540b5bf138c7f2d33efdc145f580581ed6c35c850a6c31df92473c8a628f720a4109f4dab2e4b3d66043842c87611ef2a65c4b20a1c084e07a6f6ba473d9e064c2b7c44a34bf404355ea38c600434778dc4e64975fbed14d3248ba7760a143d27bd691e6725e8acf7c33718eeb7d4d87b785a7ba2fe55e0226be3e46fd62a802e5078d0c2fba00cd25a7a862f0d213b369ee21f1531b8097268724a35f6989e45c16f084fd38813a0b00d4597eb5727cbac3dfef6399b340ead6a27c6622e80322329a57b817191333355086bc9500ce2f7fa2fdce2f3f97597d63d94d5ad6ffec180206fba1489c757e66188d2d36644af983cb90ea8a2dda933cfbed7ea01c124d74833a03a54f983c71cc29caa1e500c76330d18c152d42b9ffa2fb73f07fd4d349c0e3b1f1b34cee9bc92d8879d80fd6826d73cabf4f27a607a35fb78dd578c882ca68de62d62869eb3654a22b2679aa36df560295979e77f95c3cf8f03aeb9cf6966fa9222f9aa00980bb623dab1efdf83dd705d964c64bc6a66b9baf36fc8013380e56a0336167363ef35f6f54e727552ef196d604df463afc69db37813353ae51c6aee2cd5dbb41f9f2f41b3ead629238f6ac88a823a0445787aae5afbd6faee40266caca833cb3e08e2a4159068ec4f9981bd35cc8865831cdd74a9fe9afe332e4b7e309caa55202e859d2ef82bd22b65daa1061589fc9c59bdd3ddd1d0b538c2df1e89f7c8793c352e2953d30a2c83c29235e9c7d29733bb47d3c6b86eee392a48718f04dbeb74f5beb703ae017d25ba24128bc1857e4182a51175d2575b44cc146729a79810eaa933ee54002c2600c6667175e18d915cc32078650b2fdd97d1eeace6147c33ae0327e7bb301e5184b74a1c7618d6ed1e3442bcff9d2db2d7162de3bd2c52c88becc09dde8811f3611e8d386c1e2a5d3b8d96401f5f0431351c7fb58ffa068ba010146a3685ef19579bc91668d656e78da189078db6389fc5a764386b4e0e2d8c5ebd039263844457a767c7fc9b1b843db521dd4458be47c9063bdb0ce64cb3fffa5d6cf1e93567264f85bc96f7a204ce5e4477865ff613a425163069c38b4f1517e0e7477fe13d9be6074cffefd6537da47ea12e5a73448c8b83fb3a441755a4c3bffcbc7d229093dc3a4414535467624396ff49fcfc79507fda2956d1d28f30ae652ef6171be36ab12ba67fa8881e54535eb95a862d40fb78eecb5b6c44994ed429ff857a58594a66928faa0079c539dc0b912594d9d0c52827eb8d8fc3b774d70fab2b4f4272c23fae97d52a5abdba14702abe3cfac916d535f943130be3f652dc7426fc0e5e83aabf80c964557c2f44537ce41e636e6caf75b0331082c0b82b2a66f873f422683c191bc623a785b9398b56e870beafff7f16092609e24c71980190e6f77af2fa5e44720051ad1031a36b8bd6a209edb1933401bd75220165cfff82c50d2b9d28c5deb09a4da320023b2de12db7bbae7784f5e2ee661afd2bb0043ead55b0d1c521ee60f1b9df0827c84b1ce75a1956a5fd9468dd3335fd84d5eeb317e4f70ee08fed9efc8acb64b23c09f02036dc8563ce69b630f4445a1e307ca93885874c394370be44ec7081279cdfa9349a44f6684369bf9daf373d90149746fde5c8cdae8a93d9b08ac463fc520fc851efadd9ba83e90d956634f5392771f1e4130b1414a2a296cca8adce47a43bf11bb660ba6d0ecee1c5d6cd914aa5956a306b5a6d22014691843b5ec145ebac1d72ec411ccbb24f57d5b94696573d943c6015b63460d8975291a41cddce0c71e34d443b16895e6ebcd2339eea7f423ed50e5cc6762569df21b7287d5fbdc7ea60f0c65051ab38d541e905d29c311263587591f525431e7ed4e33874571318521c8a23560d8c0207afb47886c86a1107aa4ef5d6a7ae7d9274baf9200827c04914298735bb37d416e1f6d4fc9ff98d426cfd9a71c110beb507a1dc42a7150689d919c2ec02dfb45122773c2e5a8f78e5ce8e867df8ee4b17d0662fa6b75d894ee7146b862d456286745e2d20c7c699c7d3488a48d39d9665654596d3ead7401a61aa3d92f0e408183a6227a70095af1af51f4c9702ffbdb609eabf72ac7d7f0299f7a2d69392e00332bdda95cd1a7e3daf99383f8ff324e5aec1f8c1de480adbaecd87d6a6ff86af7087b877950f2593fb92ecfa26a33ec6872ac3f5bc65892ae5b4576554e06ced87da8a2c8c303cbfee1e8c9b1b3a451529eda95afcaf0ec87c1c11cd7dcf1912a636d03c71973fde2955090de418bd7ac838f6bea56b0dc0074fd4dd10cfe2ff6a1724d809b886f5636bccbe32291ed085c65369356c6eed825650d6cfce13708bc027d6897259ab990fa36f9e69c01d799f0aa612a08ea6f85032889199fad72035a4759a686ca9bdd86a61356512e4a91ce1d543131e83fcedeb4924c358dd47891c9437dbfef415de7cf26912151b6fabfaa779ddb760247f8968afa9ae4f4c8cb1d9f967046481e846ad04d369eb23badaf36f649ba28ccd16b17468147689073d64f1981735538647ec44213703552716ad231861d1a1f70ba0a86fef9d5b12f5b41a26da9093d3188a4edf44d83ee80f9fe1f57137764952d5fc26f88efc595c97aa07b2950b139131e49f7b79f40c8797b6bb8869234ad2d2fe775821fa86c676e8ffbf7b8e54522801582bf96ad0438da4abef4424969f7bd39e5e3772beecee0bddf9ed0bef83ff619bd3e955ad56ecfeb7d31a85b30249705ed092354aab71460ffb8d1647711b4b814b32b91d3da2dd7fdd0becdb24939539dac806d5f436b1f0bfe83fd3cb17e6d0e683110425e7021d067bc0733a450779ac7c435feeff15df583b0feadabc14491399c225afe3cc5178390f6ef08c84677539d9b3a8596751c5ff78bd9da7bf9edd982298821ec5e95912fd489ff1fccc9cf14f4b0a1aa938429ed33941424dd986376cdbe6f6dc35b0378b0279b345c1a72f4fe6836c81a0b3271af462001985b02b76b3fcb10b9a720d747b4ca610ab339cbb2ffb85595bb1ee71b15f72f5c4a24121cebe7ba50ae851a8042c4e218c490ea9488b09ddd2b238808d4895c31713b6eac73272c65e24d545bb15efbc880277fa0dab71870e615eb045de7121873885c491b21776672fc72901e23cfa558d9499f312dafab99a3ff1323e4a7a064b14324b6092804bd27febf0c3ac70507dcf8b9c403b772edc407887bf0b20da7d01275337876a2442a780f224a0cb85e349aae7d77c9bd39ac46573e16a3ded998893f362302cf3c2fc9416a13569d836770ffa7e023e7ad06460f471292da850f9932dda77db37e013064731b2dc914c1046080cba8a1f439bea56d6dba1ea41fbbe5069bd75ad2c514f7b17ce2d227841e4bc3d3b53003e4839a9a3f2c972c98e2651e8496675e478f1c89d7cbb2edc10837c90d81db5755f29370c92244b07093987c79d5780295a6cc0bb1e317d74cc6aa1bb3464dc53c55ea6ddb1ea393a08db516ae0a919c37491105fe280409762d2c144c9695c97626c8018c54cdd587ed2b56b5f19481105ba1213b67b9f755f18c90c87e2337c750c0b10c7883f7036fd83906cf653abf2d10c6d8b10c8c2f4ba37ae380c6ed09932765236b025255d1f29d2753325ef2689500bb21b861623bde355771592c31969812b72f36def2a606c688be493155ae6da0a992f1ade1bcc6ed5a7408ba8a5477d257a50e1ec00369a4a8fde2bc70b7540f2bfd00196fae524303e5b751266072313af3966ac446f27be2b61bb86c1ec594a32188447a99e35f473e9b33c7680f1ccf12176923061560f853814143b672341c80420e4be024e691c1e1116dd3fbddff3465bfb211a7aecfe836209f84640a66da351fa575474d496848cecc22cc316c0a582455fca02c1cf4ef10d70322d6d5feeebc0e2f6c85ace7f8f60731d39909e741a68e0a6084f98040f7de40bb5994c5159cfdf4b130134f355f7f648200527b06af78baaffb3cb791282deea82dec0fcba6b1b4a8122dbf9e8af0e47b726f9db65eaaa051d34ae88f854bc30c295ab0dae42b6e8a1e184d42b1013077fc89bee9448f344d72a6572eb195c31f3e8cc6d70f79627af0a2760a33525e0755e8a5a3917342d82388f16b63945b3ae964ada4cbab2b70a4aa6761189c1da366451e4ce597d0724f51bc38df56eee43440b663a369975fb434b441fe263506e7fbb6bc81286638f417730b24e7fe0747bdd0690c6b4bc1d363e5fd7ab455cd8463193dd7766fa416db46ea0eac66c390377094fe52b25a56a0599b5a066539d78f85dac1be78ac8d29b56428306475ada927754f7adc067a2ed4b76949157123328f374cc5bec5b6782c8f33b6087657282d1c9f1dee557ac0b248bd945c9c286e4991d28d2a4442811437c9347828511950d729bb08c4f68448aca4bfe480759193f0783a494fddaf3ccbc1a448cbc0b82462dab0659b748e93936eb1d46a8f478c9f66c90691340521852f36df2114f6aa435aea21e1f26ed2f896036a422e0f0f7413a0bf077af56b837f694ee6421609026c77f3cf9b6164e7e4c31756ff57e8e639e67492e4ec4bf0167e386a5fa6587680a6a35a1f6d1209574837bd27611c3db689c92c9fc522ba9bc6721320374f8a9fe87bb817be802243ec508618f56ad893240c672a86ed0a7ff8b07722c05abe6e30db261f82f2616020f44e7ad2d337a3d5cbc1a2e0468369427527ab1795af040228a63c0460b0ec524755adee6fbe9824ad4d17d2c075e0b40834f0ceab304351a9d13b2cbea126112f4b2d9444f6adc313747dc757eae3f162270e80ac6dd4f4bed4258f7aa822e3caa049506dcea88813143664c2306f8bd0c049b6b8e05c87e63caefd6f6a5c6662f6fb414da06a8b483d9169115e5733812ea7cda6c5a9b3677d5caa29c10527fed85411c9cbdb756bf5e6078bec218f81c9f2ec3e2fd2ccd28d89e0ea49649ed35eeea92471408a99fae38cdaeca03be8a1b9a9e558475b949638502311b0eda3e49fbf6e50d2a3e64c4e3315ad46d1a72580ab58b9f792db0e883af8b2c88fcbe6325a3114233c93639161ac3098662ccdf9f2324148f89c40e0bbf57fb9cc8749b75c664c3b03360f33f350b76aaff0da3ad749e22d4e89c25f9f201e9de8467fd073d00dbf3c287dd5ceb4b8efb4593214f2cfdce841b00ab3f9863c625fa205ac873af0129a7f9d95a2a075ca513a3a0dee5484c476a3f854d728c0708bd339f44b1ec56c96c5c10446a1a00689f51723af6173b2428af7238fb516ed34faf68e351223d5bde8c1d6256e2b1469bcd63d989b48e4d53a330d86c93b1a7638bc40932dc087a88f91b9532460abe728ae6cd85578ac0ab730b4028824b33cc3ae1bf0343fc92d942de3336259061a32ced1659e342d3110c5ab32f04b9678f059dbd6061d2b28db7c7173f6c4e2df477def92c7a3ca2575c0d9f04079d1a9ab4aa75f685835e3945694eb325f8b1a3104b157c9f5703df44cca0ce101d2cc78b606b2aaee2d5c787817d5ebb5f94b9752a30b56ef1eac15a0601ee157de65f87f1883d63d03aceb54d43d3c633d08479497ef808341b9ee15744e4e755164ef2615b50dae89ef47cf652312731bebbefef64ecc54e53fc1fae8b9c0720007b2ad6bcd390769f7e133ae4784c13c910e0dd933e57abed10df4dcd868ad964bba599c333702005635106be05fca042e682b14c9941b2087fc8fe249ee2df00e83ce67a400032deb07dee5cb571f280e7858c1769b2ad12e61e599c6fd9f646a02ff8b0497e1216b19bc32ff3220b517cc4aad4d3e6db34ffe1b3054eb6e66815842b4ffa6639d51fc5883a85362d97e49c0a0610312a007abe2149b67d2e9d7f31651fe3c3f801d89c248bcc77769f07d011ce13d919bd4a10dfc37bfa04a8637c06f437e150e440d7aae5701ff1e9727801c8399cad69ff45599ddc9ea39c95e8e1820380987674b5f9746511abe98b311bd3b938988f8fd86c7f0ebf7b0a9808a8805b31b715cab9ddfbfc2da32e81b47038ceb5fe419f42bc0b0d348d625b039e01e0fca9a7d3f7ba45dc47a0da121fb0c11e5e94315c38d625e149a665fc7f35a6191e880dc3a28b9313e1db2004c4607e0112d58639312bba1384f252ad96ea97eeeac075c4aeddd8822a2608cc08d6bdd54441c636494030177d6824527f70c3b40ed778657c8de68d98224581783d3f944a6b065b35a9db2f319a2f62f56a5f9f7687eb34e7965c2c7e9a049be1e1bb56690098a1999b5b6bc830304120dd6765c12374a53a45af5426c377f82d15d585b8e20ea41bc2cfa3ee2a2302bcdc711669f9f03e3fe0500ff0aecf31aab7bb22ce70ee7ce2ab4b47cc076338d3f944464f10e5b11041a606d05bbc3984113c16cea7b09056bc8b4694684c8783cf500a3601e7669880b9c14bc27836e553e86250bf9983f54a27f22f57fa1637267ded75c028f687573f55ec574e83a22d2e6b697ad85c98ba4bfa896a5b6d25c7934b9793a3be06bcf4bb7dbc8464a72137442fc536c19bfa455a5092be1ea006e0745d782a4ed1092d4790b7eccfcf8eec3f6975c30d3266cf127dfceb9520481e40d979342c218faea28d1196ae508738e186910cc2604be41206997652291aca946a94a5b771322094782c012144d9f49cef31cdc6185b38ddb3dcd2e6d4724ce862c49b3b0ad76a30b75795ca36add9f27284c8d72d7c616e60bc35b505abbe259b8bfaef515baff770628c2903a3d75a28e9b52de21a3b1ef9fe3f07923849da63f0a9579205d74dea49f791a0b2498714a03a5409c7d739c1e0b3ed21bfde7539c721ec5f76b5d1f6ce4b8160bc9b02aab2f0142c7df8333ceab75f9a5f37f5e215aa432ce41488461f7c6fed34dd2ff03ce53996874bf0a30f750ea221f9ffd24b9480689a034b4c686317a5d3332fceb0beedd0f03cc218a88152979089d0b8c2d1d1af983337ba4129b7255b801263df751e3ef6d97a088d9b40fb0949f84a19732a9d9430fc1c1d3af7e6bbdda459b75f8fbded5d4d503e1605715cc12a57989789ac5c97d3c1af15ab560fae8dc4847d34b2786ddffb00b1062f34eaf033dc70787c449cbfebe0e724699143105787d375488c5182480f70404a087995b77bd9dbb043ece86f652057d24384d344a2be8392820e930a3e94c750311270c1bfc1175d9a7de15398931779b88d17fd9543452b0f5376ca33cdd6da4c5e0e72f902fbf374a7161d3dffe57610a80d570d27af48c7982f7c74c0e7995301f15000923ebd94d2bf71307378c92a0cce5d4ceb8691d91ce9583f8cc652f0e16d491bb8aebcdfd44dc860713c7d7952cb2d6e83bc511f97c3e626a060367113019526c6ce274018323373ae8fe32194b774d1851fb902084a27225d8e47df2619ca309b3e5bd35f86e242931c8a460b6b1ea1dd419286455862c4c3ab475bb2d59113f2d693510d7c355dd3b7b86d3c733d92262c38a00d3282733663e00e6cd9162fc6c13d17d893c992db1e151d972ef06d4ab23de6ea5be0b4d2683ec830ab7b4fc273dd35430d0eb05348d27b2d23751769ac4b63e36f7d4050a9de130e819174d18315195d8db71437685166a6934b731a8ed68b506a58b2a1fb282f24c265b64b9abfbba877ef7c1f734fea666b53a42b8e32258d20d286af1bdd19d1b7f1f017cb894c14ee65263c9aab4c14c04ce46f0355f25d748094ef44803ff5a92ea4b03b6db82ee2bf181be3247dacb35296f74540667335881ee659db3da7b584e803205e3e8dab28a473af28969533421c2d7937134751a18cf455e12c2be7ad262dd68b3f4518ec7cbd34f5317fa376cd1bfaded7751d537bb387da3c766995331881fca063fbeb8b88bcb85046d27f90c7b5abea29e3ecc42fd93ea884bc9ec34f49b85a9f511303033499183009d0a5030e56f78e371d619c0f02ba725e8eeae70075f60d0e48ec2b3de0e23e46210e20e9a57f061ee326ad2b01f709e98a5c23e80c07c72cf58ef8e5af843901552dfc3c9d29c5fefbb4b2aa274215ed177f974f8b4fb208bc63ea937f6976100a899b05e4d3c7797e6040faafc1ed601093f9453f6de5447d43f142a637aa894c5b7b37218c2b29d4f1b14db6d1d92e6d81717e8a5c39dd03b3794d27ec855e1286c65c6371ae19a9f5afbb68a93360f487ca25d716368a88bf79f73ca518e35c36fd04cf937f069c8b43b0f3323b4edd61ad860913719a19a52dcb880f36f3a2f7d18b5b7dbf209eaa08bd8fb446a8ae343cb97df8c26a2780d598ffbae2bc9936482bf575a37dd0e18e1e48b293dcb1112cb8bb718bb768874b6eab4a4746bfe6de287fee19a57c0b4864889681caa2290d14f4a9e29c0f6365b177c32a260ffcf8cdac39d5997d47c41acd05722cda31f22b90d5b76bb7c3b57dd6dddea09e2fdc6a0005c869c1aad1e5eb3ddea5b0420dd004eeb10730f604d5b2e08f079c196c6152a4693c711bbb391f3a8305c35635461163c1452e941fb498052ff3467725f9812c5f6745d7a9fc51aec2263d3be05537c9d61176236d9a7d240fddf5ba5170dd1837a6cebbcac73b4b61a5fbeb21292e23e890ecbdc0722e1edc32499746365edf196ba950da1cc16a32c5cbe21262fa784609f70fa2a04dd6bf88893cf69e453a0a9e412600d64321bea87a7fa515f152ae7b2f8572ebee600f7f1f7d544ef1208eb852889b81eef8f41e3d326fbbfa88a6098886bfcf8ce75977c913863a5e15378d34e5c7da98d36e4a0259e2d5757f87972ebbe02494d87c337c34512730f2216c929d24ea32e7f76d64c3bab3970a27f9e29bf64680f28fda68db4efc00e5255170bf70860849395018b251366c3bf258e10e31d790e4941f51649f3ddbe0db2e04f95e44c485afdd341e14ecdb4b8924f735ec2ef4185f5a92cc0a08af7041e4960ef829f0384d8c2ab52aaf3db0c7e6ce5e52b2b155529bdb5143cbcfb70acbef42d2acf9569024295508a34e7e6cc06acde5f26f5f9ab9f82e20ed94eaf2bb12802114e1103af77f6a0130321bd48ddf358ba9cfa106fc221ff081bc6e7421569676cb970b92f15c19fb694f577715ba8cac868678e30df38bd92328af82f9f4375ebdb03896c2c67a9095933b5594a92dc4c5ee7eda0896e783e4443460837d96baebbbf431144b959f23416666c52a41323400ba2a99fccd6ddf9938fdf35117f85f708ed6a184babca8c4225cc3a91d22bf86e8a86797cc29d85706e073b6e0abc8c44eb2313533f3a52315bb61aaa37d7a881b2a87326ff406964b6f6a2dd8d1edef1ad65159ce693fa6ab208c9fe4c879eb9529e56c1b2985a7a56519767c21c70e4b6e1287d27ab4a47ee32390ed99f7579040c0b22e712eb4c58b8a4fc47158cb49dd7368332cc5dfbe5e935ffac5f53a926f0696b8f931cb3b18b8d6cfb27e89b0e35fc01f5f71f3a68a6b5222f65c21161e3de8a56814a8f9e9604d68c8157e11758cc54a74f6f829c5472b1ab2a464e48be1b5e68fc465589970d8409282896a64406b11e8e61ff27bf4154be92db6116a8ec893cbaaa4c53cbf23741902afc50e98fffc246059f40949a65a59616e41fe96c9f60b5f4482a5c515f51f5cb7083a0399e69eb943280ded22bba40b1e9c5a98c40e5710c37a500cbcbbd89e6630916cc46d692da42258d80938544975a99209bf35af8e414278ed58d51565384ee1889a8633d16858c865ee579676b4fbfc5ae5d33acaee3d548ae913d4272f3a3ba0bc5672271f106adac8a90d40dfd9e3c33f6038f1803f98a5a71f5765b2ec5fbdb4da61a83d3506900b5cb6daf6c6984e0fe9950e2095b405f75252f9e7a335c509466a44160aee23df3c5b712719688a15ce3b3158a2e41787945040b442c4c34c8a0eed25e6f1a1d25ccc37800b31c2b72a0b6fd08fe7d8ecbe99f67ab6ff6b2c198bd1ceb9cc9d7e79e25b8c1a0004e75e235e4b75817c896009c2b5b87bc241f28221445c238d133c67eaa682aa4883a6f4e189760defd755377c4e3a120f45177e3771db7be5c7e418810abc5cfa348aabbe401c43bfd733dfc3ef0f67d814ad512b47b768f990b6d589fa8dee519e1b95896356b71af90abd02deb26a147b0fe39b1e0904e512fd86bccce3fd697ed0ed545b7e8da1beba86f1c823115a84be0a95b055acdc6f7a886078e1c0fcb2e2c1dd4c0a38f13cdf8b96cfb8de8168f7e9b5e862ee0ffda66b11fef1991864891288af72f069a9ec3779a06fdadfeb5813cb61d937aff3f34c4b3ff0796a3ece828f6a685b7c0b0c44eb0430280d8ff01792ebd535b10d5b45601eb63a7844138d986627ce771ded5a590dde6fb8ea1945ee66e02e546124e1b8f637c0036281417ffb650297708881de9463309f637ec990dac3eb6b3da1d458845e38856a0ee15cd7991b7d80e97e451b15ffa569b0aef1f371e3269634e0495c7648b306b068980e7f6188edf6e692387386c261b4b12ec5ac606f56cb60f4201d1331189030f47345afc70fc49e858085ddcf8f2e6551df4483240a6aea9bb5014eccd765e9d8a8cbb1807ea3c9d27935c150bea15a24f557166211ed008b7d0c017d27190d6b3436a00baecfb8907fee92a4b3288f64c12c479c043442bcd6d8f8e0e5aaabb4253ea2618f4cf8c6e78e7f6983389759ff87881f3580aadec1d12130370207eb21ed9338366f7911a66084ad4aa1bd13fd9b06cd7c3bf5bff07e7d0d7d00a7ec3bdb3bd7adbe97fe5706625afb0d59400696d1ea1394cb80242744b87a84e64625ff5206a5a2e85e4c6ba82c53fde6bc2c1acaf879b5d47f840b8020f8351ce36b98233402a6787a17d8f50aa3c3c5fab4a883b5411f474c828c6af7a59cc3743cedb8beff711f41e28bbc3f677d7f119e1e72f3a17f297d157469f1ba8458805c0d5beb08940ce7eb80c4fb8bdd3f0223f14f66d8599323d5fa4cbc97db58767ac229a597e5db14cdfc02c8bc83b01dc4131cf9078148e80ca6fea2bc88bd9a1a7ce03d32f21f2a2ac32f5e853cc9f2a13a1cfb022fc76b1b7e87b354cdaeb5a44e985605c68e91b055a4daf7349d87afe3262ec00c99309ebba113c028f4ef97bbedd7a00fd5e102b4668f08c20e3a72c9478ea875c449bd34e36b940bf650dd9c8663d0faeceaf6ed74287d4ac1aa4e64f615adf74cdd1b0cb9060bb132fb99a7e47fb6eff22eced19fe5275a7094058d50af6a7f062e7f7d9df3dc910e0540c8161a71b2cb138da375434dcd7c65eeb0d052bd98b6114854812c18d14656dac001bbff09abba8553f0f92533be22eb5f62e66f67a58dbc43b910d1d4ad57951615615b28608e7d7405840d87e9eb29334f7ff0ff1d57512af823f986d2348918f87190b1bd13255559fc0077ce113dfc32aa7aa2f3a36bb652b3effb39d47e9cd91383af46d297091c2d184c5aefd9f3e96a544eec136144b14a49c6525b76b036e82db5c187a8f028f2d30a8773d07094ea4e063281a2848e7b24701d76b60beb4a04de334e78fd3c4aab9c4e6a40214cf1432dd17a64e59b24482e2d96fe700cfcd4c492b7eb9beac00990b1090e7786cff03f7e001c102f83ecc923c403fe5d094fe007215af933791675c9f9e2a9ef2f870e93848e6711136a34113cb9d1ace217ca8c93d1fe93777cf420b20b1dd94c19ed46d6e1f2d52a34634a534217ead42f996e2c17d095e1f62fb8fa8163e707193335cbdc9d690de34b1cde1caf8c9b513d2b85e7d77056dcfb89c05e2360333520a7218006e76dc79d18efc67ff60a96cbb09c6f3431a0d611991d6880a8cf2ab2eaa05d617c462ac082d339521e2bded9fe65c3fe5e20d47965c40dd2cb8621d3084e8237f4c6991239ce8e610cab68eb18bccba663b18f1aaacaac21e1b9110e0e21ab77a694a7c1084ba3cc84ad626d15d1b7022f94e994603e5e5cd93aaff0d8bc247ba2f7dabc012af444f7c82b1d986755f6e09d72b4dc35802241251b1dc7ece71d2ae969b98a64de8a860677e62513c832fc395b21ea314fa0c3cc081c437fda54a056193808970ea4c088001054a96b951705d26497988dd6561bfdc87071f89d1ff8a8de2bd91bc9abcc674899254c67bcabbbd919dac3c855200efa25605ff5240a6757f9495b77470f9a3022d3be8c84eae579040d3e0bc17947733c46e53fc7d8e53dad01e0c3642c41706b984a6ea0b78b716c396113037c6900b2d68f05b9866bbacc9dfa80ff9a15595c9a6cf33b2e8557c47c24d15cb0dfef02ee6a493b7fd694bf4b37efd8184aca3c6b3a452a21469684072fd696be6a9d187c34d24555aac044360c07043c359556f295bc806ae2fbcbc6fb64adc7d169a32f5858c380aea8eca18259e2b5a716f9abbe5fa72b68d514e9dd4935bca49ae18023ae755aad4dbcc8d9687866af1ec2adbaa6ffa0e847c2e2b2b308cd55654db30649a2f1ae901b6bde1c4297621a26a21f2f5d48ead4b8d02ef8bdc60e45ebe424116080735cd6190635da5d36f296f0a4df6980a02971cc43c10314de55f710087b586954feef020d90e14e01dc4d3f60c2cab76eccdbb2b764cc8f9533178d2d8c4fdfaff08031e1b4626828968c02984d365d5790a63b4fa774e8691a225404f2aaef2058ad6b68299c8138d3720cc4c10310bd1723e59ef3710d8fcdb98bf8cd92415a9b3dad00ca3a190d0a377ec5ec9c208e3131ec75e1fecf8688dbec9b0a62f98e2b8bcb1ede5d9c0c737d5ad03d5c49911eab912bf20db63adce011b289cd0ccd5b579ff7cb2b394e0d184cebba6a0d83d6d188df3c6ef3c1652f084d9f846e574a370aa168d683e7505b3eb73aa3bbd4eb8b58e1d1cf7d36ca73146fcb0df4e828d0fbf44aa11e59676c8ac8e27f49d8137398b96352f0fe53a72bc0cc99417a534a4762b14e9d168df2a80bf69e6a8ee2d86e51211128c50fcc62b5703c2464f48503bbf42183914d60a53ccd2b246e5ab74f50ed2a53313b62303bf12c6c52a0d805efcc71c80457c0af52d7540291ee86a48ecb98298eaa5c3c98c665d48a2ea4cb0145f3d0224a3e954afce4a2dd651d768ea88992b6665e338383011be3fbaf5d60861ace426644fad2be0622a65d3ed11aabaa38bab33e7d653bfa2d4095819aeb2f0654cc2395dd272b36dda2fd2e9ca6afd4e79d4e07bac1c05c6ce2c57b7c69c9f5dd1e7df0ce7d7e9b97ed8e88747cab032eeac308f1388d248cb3f7bdf344c2aafc184bec22372fe45f4e6693ef97087910a18af0612c2aa7ba0f08f8ea99f16d666b830e86c508be0cff706391b5940a44c9ba8fe85638d617812f3f91f743d5eba8081698e3081471140f7ff7586878a95e1c5859a37d63816a11e72645b5fa1e6624028030dab9ae1bf38a3b2fc79bb8f06fde308c82db73c0c52e32a531004580cef5dfbed764c341ae33a5003988a42b763831944204ce7585b873732aef413a26c7c4b9919ca54e765e75984465fb6d6d608cb935c63d2ea495a7bf53591dc0e53457452f368f077df4f7237f8068d942452bfa61116571b30e67f447805363bf28972823aac5513a52b42cddd99abeb106b41da31b4c1a4c8df59bf2c88609a973c48c2dbe561f09fde026daf3e07b5da3333fd08c9226b0b557eb82d2da5b9c1f169102df144baa9e39a863fbebd5f2adbebc495bd4c3c9ae0159d9c3031097d7b0892adc8a0b1a08adfffce70d3353dbaf1b1d03841aac670498ae0ff8ae15841e6785420a483eb469b71353a72ec3a3ea952be575d3a58184fbedb2dfa6b520c53ea1aa66cf8be7db12ca548874edd4533f494c4a8d7a6678ac5898b1187c915389755dbd87e08968c18218beb283c059faa8449ee791b3a88730c642370d817a54605d5ee492574b5457f27305174aad07c9120a74b3353bc29326cee4d4436b69d3c2647aaa92218282aff7f5aca870ca23c729f7d3dcd21aba5adc26c8ff29d75a51892b3c97a09fa4af9b4fac814445baac8bf38d8f251f9d780158a4cfbd4c0894b2d0712aebc236db57694f44be1046cceeb462fea2a7a8771527fc7dc5fe2b20e7c2d06c45a1d7f44f9d3551ced7d19ea04dbd705a634dd985cfab888fc7ac8036f0aa302ee5e9d7c0f133e6240489b5d8104873801cbea1746fdf6f27faea4b58e50e2a2524a10d61ae67b3eeaceec38bf0309f7dd974f755e684ed17a3bcfb7f278257da4fe174bfe3a5307630541528cb183a101872cd8666e19353d267f66f79da003beba8ce8396f6a47442f3292500cea296960732b548c97ee7f3962d05df812e3d4d9e8bd0ed5c13a98d0bd07bd421387671200545e72d7307e1e8080f6a36ba7bf997bb8be497baf028d46b502c134a6212f707ae5252d5bb2a439de4a0398de59c479ab2b9fae665308ca61f2f65fbb871b261cbaf63929dad8d6e521b04787051f4b54400c2700c690dea8bdaffcf5e398a8f4c4708a9ac15c2505d3438b629a17437ebb7f2472dc55016e50ea16045f7e173b5cbf0c8f73983978bf16078e951d0d235b79c9e96b70ddc948594b61d695479ba53ce35aefb89d53fb6e2b6c587680221c0dc88aa2c66ba06697fb76668f8c5820465b0ed157186838b6bb2ba2f77d2b90e0150fd5d57003707aff42598ebdf93e8239c32f41ced191a138b9efba3890dc445244cf7eaf3271c0d25fbcc19a342ccb689cb08b6cbbabaaea3e9d6410a97e34cbcb4394546511681792c184cf8c1df0a4e5838676513799fccccf0bc455d32a33101ce3b253fadf139ffd799d2cb6693b9402fe9166fa927627097b20097a83f25f55ca975f2dd36fbcb91645980a2dbf8d14fbbee3e31c711cff4e7d3fead7b6b1cd18a6a22d6fc86868137c693462a8eef8bb38d325c86c4b83f5b6aff095f1f7d91b6457a395474726ff24a51c2798af74c397929df55397360041ef827782591a91a9779ff7796a7d8133c35f849be1c919fb4a4b1b52ea16f1b41b0b5f2162edc7fb17d2ce97aad34c46909b4003e2ce3939607b7b88f67e849f06cfcd32c47d1004244463e31d425f0960aa9cea1d14deb23828e2e464053c1017f94d81fe0d01befacec4c488646ef1352f2ac31da33bddcb1ca737f0817b263bd0673b0669de6a5552f7154849cb7b6b3391e0c226c86e548701c8fb14f5909da5bcc607e7ab50aaaa76d1ee9d805ed9e09315ad93c1272897966990113cf09edeeb640857c18eb28e6e966e505356d34b240ff49dc30669fd135ba7f0557b6e4c49e24dd9d403ec1d327e53b0978cd59c2fc062a0eb4f5c0aba76e986799ad179bf71a910345deb849b1a27af5a3bf7314de0f637bb03aa0ba1ad0cdf8cad265d467b1aed38ee04edd148f0778308361c52bee077ac56c2e846d81135766cb8204de39146b081f84ae4e4691c72f1efa28e88f2a81d2aca8c445355d638bf4b285fabb0ed8f9418fec898cebbb025b9fb8008f9f5c4b93125359b0fad051480cd217848194b00c3c19025693f862d625b91851005ce48f6db2edb1f0cf3c232a68f9b15a01bdff23c6cdf847f61718d4902cd76bb7abdecd74e9c03d2d2383ede0fc2fda3ae22255f706165d692441774645eab80ea8bdc8e1dd8f24ab48f1edcf1d167d27a3674f6c2603f694a2d21b46afd13a35c6c35814452d0e3a419767e44301f538cad8c4e88fd4882abb7fe027067ed510662cdd5637614b59ba3c8a068a032a6345f6495d880a789e4f1c5dc6f6425de035a1b839818f3283a0d323596ad74e40a074bcaf765aa113ed72ef3318d4ca1ddcaa93e7c3a28229b3f4af0945165be0cc00320144c0f391d03a1d6aac73490b23ad9601b51abaea6d30cbbbfaab59593641d48772b128a033c7129f344b6b873b72323c062dc89e5ee9cddcdd1a37ec6eed694658fffcde476e4ab26e58ccc0b5b2aa8390d6a908a00ced370f5078a055f696837a7dedc38603bed018f960bae6db1b98c2b0e4ed8db13b0edcfe9c526b745ab40c81c0da5e85971ef0b44ee2c7583ef8b289d58217b6e304b592d0b1d5c144dc4dde2648e7684892e74f39d2f03fb25b4084b5857c80d98babb239b40e5cb1f75f0cd552dfe1b9e42df06de32b11a272ab2eb8c05ffbeaec7bf6a805c3afe5e503fdaf15062cc7cff750b091164dc3c10be3a51228e557473695b3162ed6340ad69b080fff848bb65b41ff56fbecdc550ea890792235b9f08c8ea7490f2dc1570cb25717e1e9e3f8a5b1381ab17ff6fd0583a2c1e71bda6868ddb1b3a2382b3cb41834328e395bc2c18d891af5caf2add745af767aa58e74d1e24427328b7397251a7780fb37641cfeb3e788eaf01d19952e4c1e5be068b7301cc1ffc834443b207142ff31325d8fb95507b6f21ceab77c709680af882465994b88869c6e36d149acbad3ee3377932fb0a2a5fe4fa87372e2ebb8303358bb4d9a10f17a06de96bbc81786688961bc5d94ad82bb8aa0e16a52733e9a5cf212d8274cd6469786a3e7a67d2ce2b5807b72bca766f1ea3f791c6028640a10392cba6db47e3b4a1ea89642617e3c9c5e76617317a691ea44e6f4e4cb11fb99276b838237abf91854a1b696a80024adab1b61d7e6f077655768eedfab5cb36cedeeaa8f1af4161b970ffa0dafc4a226c0c0c83e720cb1db54083362db62f20b3ff59e040851ad3ece1478c7d6f2bb1ae9e818043f1866d275601d123a08bf84bfbc7ab1375229821f3200bf41a841f3e8f7185a55361628e362e3ff5cbc376bcd1a0b4c849431efd8e08224738081fd6d70d63892e0f531bbbf3aa38e6467f6659aa65e5a7065426633d995908cb7e6b68a8036a707fa5eb29e5bea54a1307b0b2211c20c17b28d3f3d7ce5e77f4902ad9424f92fbaaf91d5a58d8efa775733d5850842bfe61e9282c43f6cf5553d8f4352c9a0ffacc8fd681ffb2c00145de970ac052df66d47ab2adb1014f5cf4f43f19e3ef2a50810a0a444b5a9bc873a7bac9e56574126221a67420659f61a6d151032731d183d448d0c3e6f25b1728237c55194073364943c9a2a1055a999bc977b9522b54ab871eb3483f909839219c57c75e1147796b35334c7650c5893d4924531fd1114114dbecd83710ac55c6be40ee255f7d2855612ff7aa6af2a5f9a56b9ed14d4fc4069b5c93ccbee393b4302e45d99d0fcfb99fe9a58b04e78138f25c0ed0c0523f568a538dac4d52940f5c76dd4687be6b7fda3dd25a548d2c128661030798103f32cbedb296d66383e980611fa73a53596a9204b0b7d82598432ab180ad7fdaae42465fc44e2634ae457719168b395ce028ac95ffaeac8b17104643d42d230e45a8dc6d7dccbf0771ee2bf343ae20bde8a98abb44f03f1d5e84b80a16c1c9de8c983bf493ada0f1b95c8930dfeb36ec17ff9c8f303aca45f15e92a04addecfbd842fdb5446814568b712f24ae12748b123657a32548d4b98d4e11be90c31862b924ac1befbae45204d884d3dc1364b95003e8e5f97cf574dedea5ae4620fa2af28f90ec9445ddab61e2241b222ce9c85c9a1843335f99947523eee1e9820c46ce45461529d6c19f7a08b2d2ec13b0a82c1b28c71de7c926f7be173d8bd88ffa50189e9eeb121d7d1271926f3a9ac6379ec36b164b24d9bed158ff1f494a3f279292869c69e73244b2894f618b59dab4eec8b175c085ed9e7618e67295e02a447078d57d8995d275df949b9a1dc7029f6a6528ffb0f1e4ff2cb82325f5cf78cef855b11ea8cfcef441fe4927d9048e0893069cd2c965e119136742f6bf259597c21f15970e42a6073f24f6cee03d51c2b344bc7b7ce087dc01be3dc860799078eb3d3ba93456600cd775d118b49209058f5bc2e6db7d85755aecfea42438c826ecb38ba7b2b1136249df8b09f5196eb2c32e51c9d4a72e46c2274a86f24399fcd20fa8c5e92c8113d01f363264ea24c2084a3d360c992581fd40765a2456a1fb8cff64c8e92df2256cd195bb3e176de175137bb7b312184d46f488e3b0e5b29dc69d6efb504a907309a79652d0b28c09fb9fa28a33e761338a12a26c69d57c3a3caa052cd81c2591ed175287cc0811a0a9a39814650d80fc4570fae4009ec6adc8546d8cdbb20dc1045ceb9a45a4cddfeb5b04609d392b5d07bf105dc538bc42f44aa2dd6dd2b58f54954c5ba7e5b2c6af395e4c4093572a26b2004bb1084621a3d4825505cac6c2ecbc7189e466dba684084597151970ff4a34ac8e7ed717285d113ec1d6d3e702122f697c34f38693c3923e321dc22c1696e1928f9d4be143cde455f07fb456fb7fed1399c3a92213518e57ef5ca073b211054368743b86f90cbc8dec49f2c3395825a6a49f30ce2d91217975d4c85956b8bfd8f1b00c1c274f12c730a63defe9fd4ca1b2e12a28b0fcc2e39dd26b5705e5968009281afaeb905ca5ce231d2c37fc5558040c1094648334562141529d5a511549d3ac6ea9b2099288c80f9a6f89aeeab7a7fa83bc897a3dd4315bb28c34b781913056f9bbe745c1277ec6d0a81064e6b35f0cd5651506df60f76b5704e27380c2e0e7144a9003cfd9c419140a8edc617ad2628f79988a73866aea05f4f65088ff00446f641825771d8950656300e5af2ee059d790000166c15bd98256eb8f5be2b76b09d31669f1e38deb4462992cf206aedcf280d1f4bc66a085cb4c2bd45bded871b719a8e2322bdac14e1d2d0825eba43235ec27b6483e5fbb252a15af6ef03fbe7290294a99a270062894a36731b9e2e034e664938870ee59b64688873823cd0dabfe3f0d02aa2654517e16b29f34c73948adb49a7538299dbdc2a3d510debfbb88800f24d730d569fb37a470cefdc4058554de3a8059050129573a669ac983aa3c4456e7bf2d6e333527260a0dcc5373fdeb30687521b2ce3b0e99329c849c00d490737964d8c528b195ad0506aebf39806343b671fe2c27f229fb8e757857b74194a9b3eee89721f02944fa5e92fb3b0568e087296c7c7d4fe5f600b3041c37a55502d67e0ed65fc24a8b942fa60f8dbb0189eba45afb211de022e27fedfc41f816cd5cf243ffd784794a5ea78c44edadf3bcd5eb17f8f1385307d7b82a79295c6cef33a2b5bd95c2b697157d90f884c7730a5b10591c2861a96832682973bd4a5c0680bfca63621a889b241633a6467c76a51d10a0226ada57173536c316a0edb1137beca868ed86ca0e9ec2b88589259f77aa80dac632974188120a16d216a0c66f4b50e75a563ae036c3c30abb440f1f49601c14596e3f50e5bc17f672174027eb8ee1c950711e87988d1d93cfa3cd22336f39f80086a092459ab8235330ed28703a416fa5d1025bedcf3477e96e7bdff33dadd4495189238d400458a25ffbdeaff07dff736cf4961924672751aeb9c49f77dbd0ba77918f34ae8cdda2934f742628280d60b7a399057099c4cbefe6732dd02a59f9fd0085c6a23144aac67fb83165657ec417f478af7571411d5b5e9a97000452111c7a197a7dc4177213f845a233a2ee8fe7f5c0dbf58a1ccad8f5dd5d7285376647cdf10ccb72ed422b083ba23af393571c762139b2ebe4fa7b90e97dce8e66e6d24f0b3ef1ceb72e0fd1a91f53a9f732fd4aec1f062e316bd00ab598943ec8d312cf6e28d4fcb3d74deee8f504ed71b7c0baf28fc99fa06b251e70160fd05f3d164b30968d06f49686ea95f3a7ecafd987b54b063f949bb1392a75d15edc5f40dfaf951c51eabb85135219ba9f247c0d14deaaf5e74b3bd11448ee32a209a681f03a754b1a088a4becae0df22d25b6f7e8900ae2604dafa325740e9d4beb0d5472096cb3b88cc9d8e5efabb62c6a53dafea184a30e62d898a9d40b544b532a26cd63de41231e0a48f015f6b5e17b83cf0aa3fafa872a8196e1c709541a5b89ef43e1f39659fd9336eac82c7e99de99b88a9a81968e7d30c24fd4ec490532b015ac31dc9515737204816a6e5cc77a0e92a9b417211b13769b7de5dff4ca82253baf89a5d1cfa89aab39c562702f89d7efaa149b390aebd1ec06db2c80e0985cded83e1a9c93b3f29dfa56f4ffb096af1f67aca9d72d9630b0be4271f962515af38bf68de1409cdfa9d532bc3163586b11fea15eecf2809448b8845852f690df5ffdf09d1f1b4666a2739aeda0c2d2af9f67147f7184ffc2409c8dc6d6a4daaacbfa4ed24b79fd7fa689c0f2848fea0b4f4fb648693ffd908f34a0de67c89512b83c39429cc7350f9c10e0435b5bc9d1439c6d041224814ed014ba009e3688f61943fd708bdc7111afd5caa5bcf9a55ba1ea3d06003a968f3f2b7f7dcf996811cb15688a1ccaa17746ed01dd910021ebe7cee828ba7a9889ff0335d000251f7cbb233f156795124158cd86ad8430dd6154f4bfb47b31455f666f9613742ff1dd5e987603896548b78af905d5a82f30402f5bb5d8d788155d771ee0937b902cd813d28ebb5b3bd90b24c0dd7c8b85360179ebc6ac238cd0d4d729675bf7a565341cee9f2df6ee7cc467d70729a06fa43ed2180503e6f40d422a9f00e9390efae87f13ded0160884063bf63890f11c3e4ca79cf9e338744b35b736e35f5408d35aa1e7509a69f46e97ef0c27e46fc109b9ee5b6c12a6ca0c083fdcc8bb085b51ce7ad64a0598604751bfbca5abfd0db66f82d18f398893c34f3955f066efc96de3683ea9806ccf69cb3c931a144f3b785494f9dcbbcbcc598e3d0d43bcb82a0b44f06f1cae5ff418f4bd20119b284d5d1c61e8a128c8fe3dc3ec5019a2f8ce289d72ab180ba84bbbdc2d628b970d97c2c731e05c79d29de08c3a37eb82a1a795c6b7af5d09620f8b99c669d5cd2827e92bcddf45c61da264a82b1271482ec82bfcaedd0c0c9d8cc5ff71710e4216b58d89d5b78830e1292f0dc05eb9de3ae949f4e41500b53e3a45946a03a08ecd4e0e4b2318fafb802736b4fc9a9a465e8cca770ddb2a52b98881fccae5f38114f1a7ecac3a49a7a7b5c1a4b50e908584cc06a7e2cfce7abf4776a29b1e737b76a0a8e873fc7a7d68ea636c06fb158c6e23d1039ce100ba0bf67951eb61dbbdd1dabcc93e542caa1ad68f4354019a567683ac3725c5dee3598b0480d02f45a6e51a8499f0d2bf7ab8ce1d20ea24b05f19ef3cce5ac5cac798f4a4064ff9b3cb4a027fda8e53d81c3a3fb99839ef46d0cf82a492f11cdb59e5b7c52f12ddcceb4426beec5e49e04465e92b305fbf6d74231854a866200caac7b1759b5d01f7c48760ea59b1f67ef67b204fdc48ed4f1994595252e3baeaa7315231a6a54c4b6ce265931d06c2b87821bf1c9f8fcf458348f226f7733c4d848eee2ed1c1188865cf2730bc454d86b46ecda2d74304e32b8edb649afacf060f57a0beb75566a76efbf92ada3f5de84d814d9e6cab3222942855fcc8114d8e192267fe44dd996b3c137143217f0c3710036f2599ccb8cc6d0f570b9f0f0f1ea812f9b5f29d6f4d90470586b3ed788a3d2fcddf277da379d168d6989af9dfccee5d1e25715fe6e40917f2578326522a6da35e627f066738de689708c37d58c28bd818b2d634cfc484127847847b35c0379414fe1481918376235622f16737f3846c1dcd578e2a75885f948311f67efc10107fcf1fce689df0e44de648b61a489e7770543e4b22d61ee6590f7a9f2d77a147c00a224ff27a48d91c8f4ef22e1cbe31dc6daca093cd44434eedcd957fb8cd323bb2c46a55137d62654dd54d7750fd291781354c65eb90fc4182c0a42968500b4e6e3dfba23c4f04f6b3469ff7e4bddc75f2c5c6a43920e2f4e9e559a9a4c49246ca39134b9fc7e461d2a596c1971f690e00acdb046d435f0cad067a5ce93362b20f963d06bc7957723800ac711ac51131dc9272ac2f71593eab16b1480308d5e1ee6494065482cb7c803003c031377d2d656816813ca25d4374772569115d72a16cc34a612e87ebe928ded5d6f9edef69efc432ecfafc87778746a5be54c8ac86d40767cfe107e3b525b1d0d4c98bc93fc9b195a41349fe9091012bf09151661e60963592c51772092c9ba66ba773ce841586dacffbb8700f0255eca1a9a4f0ffc3b1eec94fe02cc6bd228bf872df792e5e9cb7e231c622676018f804751ad585d750ec9d39c36d525913ca35419be104abcae94d56cb9159c2b598b2894971b5246d4fafac3f70f9a91ea56e3a1efc0379d2caeae9726e4ed4854a47bd1966cee039a5c0bf5809ad2a8156d90f65049691a8be0de7985a8b42845be65af3df5ce2ef189f8d8e112b7b0ba9da6cfd0d60494ddb8f30e83fab20e6cbacfd9e785702d94c387d830fc345dc916bb929b8a1add590d097da66f202171fdd010ffee7885035293f98b8f7d300e600477695c9ba3f21e984a41ea703d395b43c6ef793d072ed9d7121a6ce7e5f147e8d488810ac4e821020bd87bd9c77659103237565a5ef8c66e542b9343760b70816a3afd5932336c7e7ddb3a0ca9740fcc84421740fa01856d04fc4f36118bac740692bee813f25dd7ed8e375732fad6fc49e10323569daabdd2fa5e739ea1eddd91e2dcb7e5f0e8744f3cb350fd06bdd0e6eab72cdec810665ce3d27b354ee402799edb6940f5bf942091b4dbd92eb6d5e82534a6b81441a0febfc278b55110e8da9b7f68d6f56c361f58024d6092355153e53c89a9e2aaefd819f8c18ef76affb44cae3be6d25e7fb18a0cea78cb2d59fc71b1529a5db151770e73767f909ac9163af748abe8789068f2b328805edc23bd52d61a080dc3e3f5b4c9ed59b662b0c8521df7f655bf20410a336723dfce0e860d406e084b555d8b8e99df64612783e10ec4754ca37a042866e3f19713b681f90d6e652e29721b7e313586ef829d94f5b7279a1d3b124942c6d08f43b79d2af5cde4fd318f25469245be01c9f98cf72a1e658685fd913cac59d2c6743a189253ab8153372f90572a8781acd91e25655bb5d960fc0eadec16b8322afaf5bf09c89edcbbf1478ff8137a943276a1f177bb44fdd23b79a6efc354ab7971e7d4fed8bf083ed43b42d7bed632e562944ad8ed93f022953e53628b024fba2b72c19c8be2d5f0522e20036f60366ec0411a31ac76352855471f38ef2e6bbb416918fd07d95f4c7efe4d0d2e8fe1aaabfa886324e562aa6cdc96dd8528dbd24f8d00fc2f8430a179f1425c7faecf52560227f0bcc2b65bbb7ef9c1a2cc0c21fec905a0b5ac299a65060a3de7fd440daac8c6b767b43f97aaddbeaed898a4711c511edce2321d9ef6b0e08e342942654938fba3a3c0996690f53ae189151967bb33a21998947c5e9e9894603372de9bf5f17bdd9eac9f6cf283fb77df11a1096568abbe16dc582b2590c4fd37a15f34715dd465894ed7e8a4d80f74886e807794f91afe60f1a0282665c5a3ce296d3d86d3d49bd9c5ec23db248a9246726ad8a31c75854e6b71605ae5546b9a75e40f9d5795456d88b50273750d391dd6ff1fb27886290070bb0bc219aa91d662f9275710f0c3cb436838af31f5fe7d26e635ce873e4b41b8edfb1eda6ee1af69be1e22195d0e64c72332a29d0c246d6760a02398088eb1062f5739ef4674528db676ec8ac83a8770492d39d4ef371f8d1c9993da4d47fb1498d8fb39d3ebc5a99afc4ed11ab32015b346ab1fa8c533a4b9c9a56f3501064f92ae48ac0989a9f4004c994a816fadcb44ab0cfb065c9c3edbcbcdfae4c14f86d4c83e7a9363aab9c0a999bfb33f69dbb1d4f7e9aed246e02fe810adb91b72fcb2abea9963a914a02b3a3c5e888e1c0a7a8693143107b329e5b97c6de6ea39718d05482a8cc1cde1fb14e48e3055e927335131ca8136cdd4897c6b0628b1592e39c59539553604c672b134442329265187505541e219882a920d94397a7e894ee8a6cb7aca54833001a6dfd4f4d12a7a6178bdeca5a156014bdf6a85bb05cfc8ec5009559e45593dc2ac5fb9c757b2ba4e1252465dc15f01a7d7b06833e9fc17e1b79c1bd3a1a0467a068695dbeb244f6d3100fa7df8d6be41f403edc53db5f68b28fcc99a508c3ed13fb49257a1c78205d70008383f71e6598a4516e0da9eb2f3d0dd292fe6dddcac8485472d45c573d1d7dc4c49873e1f7483d3242709811f3dbbef85531a0efba75d00161f9288db1cda9240e0d129847000ad5e386d3c8de4adf32f434bb6096545f0fc6c22f1274756de9b8ceef40627f998508c2c7378853afe50bbb9e0f2e27a46ed0bfe795db6fee3215f38e58daf1e1e90f63ebcf19cbdede325aa08bc4382029f746c286c7b874ac43b02aabfb2225f9586d8d69e38e085f0dc81655009fc7377da1d920722346eda6865a34f9ce074f8b1a6075d858163b760c2f6c997d6b9ae83ba09521287c33ee2aeb93b938ef00d59561c45b231863fcc5bdaef3de33cb38e01fd2dd58dc7f3510dd2bf1ca59a5b0fc675819aaf5837ad65997f87ab080cbe9b3e26c96b8ca873477a77bfcdcaafa4e91c8a6bd1067f53dcbf3f35a77e7fadeb01e42e4f2fd80ee4eab1d2978affa27da40c99c87dd7ee5ed030ffa79a2cc33de5876440c89f0a44f25a173b5911d5f5a985173f4d6f814c31421c165e38ad826a1336a15ff422f3a92cbf83953aa60384bed0aa39669602a184a554698745b6e168492f0f2a085bd907b595fb81df860cfd2afff4fa2ef874687bc0f98f26ab375dd002360dcf0f3b9c042bd6ce85dd9bee31c2fbbc3091841e1a1c2e4bc27669fd3dbe66a6ab27bd7315d35286433566a6f6af6cc899d516394daf632cb6a64d97343257a03a61d6e49659037626c31e25e25d737cdbedf0803b041770f8e68f5c6276c9857e1f895ce6afc7cdbe9ea3dce51bec23781a42b0e10b47a3196bb490a2bd5bcd5dfc4431eca57195a314aaa8f19a6b1c66beed819e5d29b15847c33cf7d9c0b6acfa0d6ed6ee881868a860175230dc9943fd835b5ab7ed376e24acefb6801dc8ec7c66ee71f58f590ea937aef0e7abc6e38d6b1e29725391f3394ab78c699fbba8e20f2300827083fe692d31444cf6ca69dc429b39e048514392bc8a8d983f27c2eface07f722a0df900fa82466812eeb9ae371bbb52d7132bacc0f7f097c3d6d10f2fae897d613cc2f61777c31660422f32859a38325609b386dc3000fac54c69366f09bd0067d05dc333c7197f16a3d3318e907519068a5181b81fe1e736772010144da745696c561788e2cd94543a3151abb481923450d1d7585bd30765264b050ec203efb40206021ce2d466d375b2da90215569dc9632376b5f0ebed0cc7737764292712106a99c20fca7bd12f1d8a150236f35b5beb9b9bb273b5273af11cd60003cb1e6d42f4178ce09bffd2c36d53687ca0f3f7d4d9710cd8688e630375dd47fa4df15f40f976d4a4775d66af5928390c16b4af5befcf791e4b72b160a69f2c4e641843070468df0132f810da115b32eca2b34bb493d7843d53ec7933600a1136596473489a590efbbb29e4ba405f80f23430be7d09af924b3f259657a07252017303401ccb7230304c88c621099a877e32c333718f6b8992a34758e0fafe1ac5078e577b3409735ab759836597398decc9dd0f97e5763d6d724f91387eabf883d502b96f3ed7ea3e273463dfaf4cfaaae267d60d9cc576e6a8c560ce3115c320ec73b9f6b51e3f2cf73ccf3b0150e83d8e49e33d8d8c56e22160d1f26dd05192fcb6e69dcf30e4f6e87502d7c09b892e3b476653ba10bcbcadf8f36faf27cf84f4635a38c4c30e9745585345217ad1e8e91801a46f1f6e43d3eca12305b755c4958e860ae0e6a502d5518f9bbc6d3b73d3bb421b2cf5ad2ff8a008af5c26b4c939765f4b05cdbfcbb919d16c6fd63a12071de8098ff7eaf7df5071b2875bc9ab8bd3dfc9c1af1c88426ffcd9bd78959910616be6a8a1cd6a677017dd45669baf2f36114c3a8dd7d169f6c37873686132313b1b6f22ef0162f9a99428560e51e585b674d583e80650f410f7dfa9aec31b440e6c7c1dc78103611fa5c15c4b7674136b8e3d0b0540d26288eab41161a674fe8213dad172c3ad90fa49fd47679473c430d550218dd2a87da55495653bf0389aa7085ff3f3c933d0d5d3d0c87ecf456021670f711142a9e3c66b26e2e8425338bf5bc9dedb5bff680893aa36b8c8d2d154a2e71187aad897a1b16375d3cfd889968b0990fd33fbff9b8d5a42e7291775e5fdcc74d4a7d818950c90a3fd35d44cb906d80edee9b9c64fd30a0ae310b4daf62cbb51ec3a3d8a15c0cfb7c48f4515a95535a1c814e69b094c9c657bc531da50f2e2e8aa8a39d1a4bd4a24a9e3dd8ed2b24f40652c4bc1940293e857d1d9eab95a886f843d0e05890539b9aac60c1f18c1a9886f78b1d4b40952f82c788d329d719301e276131846d89829f8fd42b41e36b2acd4e73d457c636596ba50060b0be7d562d716782dbff81ca527fbdf599398736043c8cb5d8c99717de7dc22446d9ea3e9e1e6646728f9e4ce13f387e6155d826ea26d875df28f11809c96fdf89cde40fba6126c08330cae2a8ebe0270aa9efe957bb6e0ed418445e38049fec5ec2e9e2c06cf52dcfd8669ac2843f6e10d5135f257a55f511bae8165e1f266c25dc432435ca4d46a54b8b5b93531b575464d9894e78ca874d05acf16c4e28a248e959578d1f59f736894369d20649f179b7d058ee6b66eb5056654b7e5122e85824e31ce50720b4cda11ff98512442658c32b0d31faf79f392dfb2334127e46cdea4eefcab78741417eddcbff4eb2e177b16a603612c8a2496a6c8f5c0f5764a218decb0608cc73fe33d03ada00d8d568d24794652b8de297c51228682f168de3d73b4f264cc298f83be988452855aa79ade8843947674a5d0fdda2ebb5a48f96f472dec93e771c4cb900850822e8fc39866e2016ab5d933c4e5ce1cdeef8ab90699be217d1d6023285937571b9514018517be8a2116e80bedfbc3b3f637d6d7608b011b55a6bc2d820c92730cbda0d139fb3a56787a54f766774a0e58ff0817b981cba4db0dd9cd4836285fa61397c49960f2401df0b303b1800698bbcebf48e250ab09b34ceaeb3f8010855b7a9e02173e0b73d122b2c97ceac156d3a278cde3b87ac5b15491c75d3494234d7d64d39f974da74978aab5d06fe4428ad2f513d8d85f26bc7c0937c4acfb4dcf35ace73ecdefd7c82c8a3f5d5f2a2891cc27c49a999dde2c97006ee9b1f91ae2a9014a4b7137c9267eebc1ba92ae8f65cc321726b29420664a74deb37da5e95add646271b7fd88c77ee5678099ca05e53fee7cec792e6d906e17344862618cd2b361b9f14c2ee590eaa2c53e4517560abd5a8e924baf8ed01b01f74482666fb96fe7380145ae1ce21e9904a43190ab2806f74df7b8d8fce830616949b3dc47f7c59e46c2307f93cc65d4505a57c77f96797db1290ac42e0e0af07f9fe70fa1486050a07c3b286e55c5a8b3f30740dceec883a7aa31c8be2947eac4f464b05aa4e675e909d8ab12992483a7c74aaa9376d7b971d4a881ede3c8a73484ac0e9a4629f2fcf1ddc83c499054ab512b0d8c823d5e93092d0d949d0f64d8d722857b26d207e004e1fc57f836c89fbcf7bbe72b0f3163ca069945b1f6c878bc81ad24abea7f4b89071641e610e16cd54c2ed993bf4eb6d40c5cc84b12a02f089e06419e6eee31ba2bb75594915da66075b59245ce4671e98657130af29acd5bf2adfd4cbb3a02cad5bf5a5369f182220bdf3dc0cdfbe70d7b458df565280a285bed7b10af41170ac2d17bee610777708536150a288f7977aabbb56b72ca3c94930d567e2e0c002a1297bd05c708c06650cb713ce51c0b46752c78d072c5fc0a1171586cb590477eaf144180495baeefb51f0383c050ddfcb924c2be4ce0872a83d2c760212ec2d80654c0a3b2b1404bb2f3fd430a9d8c791d04b472067c1d60fd54f3482c19273e2ff3e4459cadd044e69955b4a4ef779c2d2c8dc7c87c4db44c62ca5975548694c3c92c2041f38227a9a624ff394aabc25a8c2016fd3848f43ce2e7a4a1aed5378634e81ee17d0030ce990f38b8fc69d05191fb1d4b5b58714c47c7d80f18710e357e865c7110dfd86b27784194233e3c8a959c37f51fabf096b78781d1bdcce05ed1e4483090ed9a8e624e93a1f20fae9c66c14d07c386f6390fcb84613f25e9d9bbb36d86461006e98d4a61802c2705b6f1c7c40a0440df85456c0c55a07a65c308714843de7b30a32050b536db2befbe6b4cad74e64a3dcdfea04607e1b6c62d0d711ef56f4085e93cd0865a91b53de676058b28fe19844b031098c3adcaf622e314c5288d869168280f1034c44ffe82a5df0c2d6a6b9d2f17ce4edcbe7420abfa3545c16029d6dda71a60973e87707f9535d78af64b4c738808cbf949addbee9fdd68c4de4a9d7a28d260f85401d894ca3386112ea899d51891f15105a6cc783247e54d227ecff3b1330fc220e876215f12613519ce4da89b03a9dc0f040ba8fff952f37a23386132f8759d7d3edc1e038320ae6800efcdcf4e1b52883f9e0b638bd85d50d0a9740df91f3e6f9f58e4a20b98129702d9f9901d7f4512a7087b0d79f000c7ae24dc74ba456c3b4d554fa3a72b26b9bcbd8387adc1a24eaede204c9fa9042bcf1e38d2706899c700fbb2570ef382bb96ff4e5178a973f63866ed891b8d537591ccdc7dae544e3047f4c1f57de2fc8af8d7c068165b097d76412c11b9f1bda8cfb4baf81b202c7e193c3b9b8efd132042b9495a586ed6942e9036d2f836d20b67c38ee535eb8047727cba9ce773b888258029c3b628feaea98ce7ffa0709a08ddfb66548ca68aacaf304d218c50bf3c850f48d46924e79690a2f30ca17f517af03ac2facfc72154774bbdc80f2ffe5cc74da829e036928fd9c683e5f9ccbb79292954c6c6ba4ddc683bf26b14608044f30680a93cd27f1479477771b2db28d64a004d65bde3f8ad24bc9e2ce703bbdd1a793a67f451ec4b1d268ea53d66b78bd69936c54d3ee0e49937c146e13cd1a8035b9c69e63a9df3c4911d947b4c33e39023fba5fbf7543447db96cac99918d7b9f50289f8affe2fa8d6fc2281316b75367bd7c4460de421e9a73b7cc4803b73aad5ecd70cead51f22ba075738f8f1608569df5d178fd95c6ae50a20830d1c26989dc425645f4b30e6a2c39bfc1bf38ecab3e4ae62eca13ab06e6dec7e307d31c651e023254098c7f6203713de833ef3248ac51a4cee51c591fa78b3c3f0e22b06df4f581ba7da46877ee6b46485d11611fb8ff7761ba338d1e14b7c2f0d9a4b014f5e4d75ddafac6a97fde7026bd6c1f43d160351b06b188253f9822afab22d5ccabe8e1175ddd87896bf51d452d2382abb94bd1630d92715d8a5d95f9006d4a18672544773d7775667729a2abbc5a48eb015b1c7404446412647d7989767a9614ce19144ac460b6676eeb84c458f2a8088796b3057221c69b2f38647ffcd7346abbac698df92d52344fadc80e911ca3f1691e525e28f0737e9e7471b1e0566d972456051d7e8db67b74f893420cde73d4df07cca750f07df18cddc74530e9e47d48b2b8026c8104ec503b95f96145214925614d8aa785b43ada41b9665b85fbb2782b00342f14a324deb4df357fbdfff9552752080379980323ac3a18a83e301f123134ee643529a4cd71a96ea1034d91f61f78759c192680487e3a0f92477652293f605e3a286b70bb5cf1443288c4cd8e6d085ebc83664b28689a7f4a0b495edb4a43986164a2d6e62daac6b0be1878af74420d8d39e6f01bfd5e666867f809fac5091db156bb75c15abf67d4c1b4e4b91684ac49ffeee89092d05e7a347f2723ffc693ec2ec229b6cd39cf5a3c72fed84cebf84833636815d409c7675be2439fe71eb3d08c5fe149cd01a68a72016dd872a836548ff6f5345adc97427b044e2e09bd3ea6edd56584afc546314c3b38b408ff23391796062b195cb174ef7e9d853db0ff6217c238f350d736c3189a6e4cf04ea5c08b8bade7e78d157dca5b2cae8bd2c5908fbe88af0ea41d858517804b275529bffa25d1fd3032ecf774bf5bad180ee621dc791d14c46a8d0295cb469579fb62e0663cc02fcadb24baa2c1c399470219f31abc819fcc75a73d002170406cfa93386c665d2558b19c5e7d47d40c3045a6bd294ba8f644c3115185abf57c50420d55f27c9723c1b5b5f3fbfe7cd3a8914a25f3ae441b62f930b6a29abee21e3403bb03e4d41aafc9c854be7626c6cdb6bf7b9ba64865ae33e366d62005245a4d3bace4aa70b5f64186581204eaae157adbf771cce093fdbe0564ef529f9a6a00afd2f705d07ad384e4b56936b66c323e4d230b2b8beeb05d9d0866414193ae3989d8480899d5a46f6833a1a5a576267370a27a766bb565d1f2744bf2097eb0a2179b49c39c3929c6ccb5ee6d3ca1ee3e7b021cf0d2f0a9d839c253f71245f31a5d64b97009e02fcf854c65035f9c861e2afebd961c644a38ef9153e40060c938152d2e4ee7e34931ba26feda7ea89ea08fb8ad1d7cfb19afbfb7b25714f06a3cb56dd0efe6c5764c97626373b11b2b436655989af3b18ea439146997a8ed13f7f097061e7478a0dbbb79206a851930c90063f54b1f1b07d66bb9c7bdd0fcd7175670c19ae493c6621acd5618dfbdd02ebd5c74c4471e7cdbbf3febbbbeeff6b4ff8ffc7cb25c18ae7c01ca0e3d9206f04c95013ab66636d59cdfe2b523a6e1ab99ee0668fdc5fc882cb21ae5d336fbde78eb932c0e7f26911c6e3c44525a8333bcd0efd48e0fa5f5da52df005874aa54b553fa57df255d38d58bf7b5a95a363c883cdcadc86794ad0124b57b813e198db3f895543eed83fa3c98d1de1ec7376db1976c1ca35617444e4cf6b069d25608e24ef3608299ff335c61d984f442ffb5195476c5b64672758175632b2c18acecef10ee239e217da43c7ecc78ec1447f33e7ee80c7b563da083677cb33a69dc9213380fb1f7a201d9520f13b5eea933ed88bd8f9668156c9d04982ae59c1738fc7543e38a8563a8e8d00efae01262cb9d46dfb15903c12d7a606a37a14ac8395e10420746c0a52e7e21a36efbfc101fd1147dfbe3cd67834225b488ca0933dfc70dfe63323fe62e85d7d9e8ce4d1e357794f3fc679f5a72710f2fd38b3158fba3ca4ec5c66a64741a1501945e28e30043b83ed0f7eb0cfdec112158fa0941355b30b15927ece3024eb815b95de7ee14e7bbbe43235dd7b06e54de7913e3242ba0998ee5e798b87abdb67d56f92d4016a9d1fafa2d700fc66beb1af588ac6b8cbfc5cefe27722e84e4be02152072f1d78f6b6cd5dcdf9cfd4c39c557a9ceb50f9fc0bfdbc18e2ed58157882dc7b17d2617faa2f9bb39f796c576ce14721ffc41ec210b1be3cc48c2e2a01b459ed6255a7cca380d2d489775872790af6691106f8247da5c5005e5b6865413999bf2d86d7651771313a16f9ada5a20b7ba2463b3885a66ec8cdff6c84581b594ccab693ce26d08b8d402cbe77e4c80eed79f9cf72e34e6457b5305350f3d357911984c400d616a6fee551ec8d6e030e303205b74f899f437799397edb8a1c5f554d089ef20fbcc23c0db92f5b87584d32dc4835d8da6a3b733024e7ddb0cc31b328215307ab5d4f40c1851886eebf507fa54c7ee8ff1bdb9e753e4b47c81f268d97ed860a67d6a6c89961bed01a36fbf51641dbe33ef468e9428ca2f5a424b2f461f17d6dfeed6041a4336ad3a9deb5d580633f2d3b47641b962879c73ce885b3f5607f464feea34dce6a84a5520d466f54e7afa4d648de015f7a6fba58c51cfaf1ff5357e086d02cf49cafff23e438153c64b06a11134c4e035a0d0242d9f42d594114c207e2159cf2e237e4a733bae494c6443e0e906cc963c59307e50738b4dfbd47bada5fd7f0d527363fa69ba0930aab309984e3368fa37e8236d5bb5fb57e785cdccab7c9c5ab0e3e000e5922edfbba86e1eb445fbd278e26190c528501f9cf10294377f48564c5a867186ef198e654d8ec7aae38bbcfcdee24af679feab4b2c2c0831e079a4114e765e0c6c3079efe3c4cdf5f41acd04b94182a00085ed2a42af5d967ef8116ad2242871fd8bb1efb91b28aded20846c706a1375dde569f9feda00c7910772fa10793e1d8964a6289cc5ab79cf27bc9b9e1346b6dd056b643e22873b9242fc5116dfa9db5a9135990d0e086ab04c022c8387b8d12aa6da3248f2ddb9ed2846d4ce60e56d38c0555d04ac879421143e497be1251ad2f12eb9aad792ba8722e3e3a8e1c7e59f60f69b7f25315871e45f08215ca90dd6809cc95a732095c3b449d3d45de471dbfa60eb5dc772b80bad48d5bbfe17e7fa96030d8408d3722f41ee2025aca7884afb1c962b59d8a1015b7daf22ef2c3a4aa4841babd683f3bc86731fc3cba64445edd2b23a0a5041c147378b5f851db484423ce1f7239aa17603b68928c48e223cf2cedfc241c2160d7d1db2bf18cc60eb8ea5c0130da6045469c4703f546052663b267f1c979cabc22dc824075a25354fa41435c9896fa9d3000dcf11a3786f80965de0abaad0a82efe30e2349ee1a7653585756950278db12ab17d44f10186554c34fa7ecee445c328946a222179aa97a6b24e4c4c859ffde9f57668bbdea91025c602a74ed35270ee250173dcee1a3f4029fcb918a8266b2983846940148218ecbfd90bd3a369c738abede4a26fd021c31698367c0c01e3e4188e3984d1b0ab8bd3dc62dabc4283657e95777eff453201b1a5d2b3ecbba469755d6d3d7a1759df5f16226fd60e0a41792e82ff1ee9caa83df64c4141b9ba5464ebaca19214bb0ce9158c596efb334a2de33ba442ae306fd39bf9b9170699054d2625e7d390d8fa7fffcde4ab989d0f58b6b851f31b84f73cec5f7e31f511bbb6eaf0600fb127d48fd6ad1c84a1af05f8655727809fe2f31ec14ffb742913c5ebdfb147e89bdd4874b360601aaa2ca4979685a411a2504299df1a2fc2c12f5c9443a6a5b20ef588437b296ad9ad68118bb29511658639794e26ea6a40b0c5882b4b3dd0516014bef97e5d93b95224b5c7c2464eb4e43a90ac474ba14a447859f4cf3a9e34870663a221cf775a91e75ecc4dddbbc47bcee1d191d66209176260bd4605cb2b84ac3d5cdfffcccec1f7e565310dfee2e142e26d472ce055a2735a20c545fa916d978210b94fbdd390810d053cd077d777141c81498731f784c5f1d87bff2cb4f3036f1afdd049d0b34087ee78cf488ef5bd351e59893ac58697e41960d3db0308404bbabd4f387f36dcdde12b7ba927ebcf55ab430081c4bd3c9a02b9b19250e7b2524472409f2c6caaced98b97ec5fd45108c7fbf2852ab2cb6fd1f831d397fba8505ae77e21395d374c83753ec9043459f81bdadd228401001a33d367d2e722b9dde21d53defa3b3d9dcfa6cd1fe6dbf3a3302991ba3cae32c55fc5e0e101b893517aa9e8251dabc5a47c0ccd2a29af0f4fcf728baf74c600a1ef1e0ee48822607789b033e859fab33d914b4870073e9c10940b9a0f6d5c9227e6e1166f559f5d3aa11b5ba4e66809f485d6b3ee79319031255fc1466065021831b29855d4d6060f8865888fd29761198fc7e370199a8f0ba29711b63b60fcfc073c8f41d7e89f17485b5d7edd6f43d84209cb409aaf87ffa3e137159956d946c1c65d2539ad511a05139cd684d4e252d30a9a8d101b890c14223c38f6f7c19d5a6370155e4f0b864efd71ddbacf6bb55c6f7ee063bebf324210ee1d75aeb42c570595afd92009419f7a70c8d09cef6d5c6d0ae626f2e1ae52b2b465afc761e0b3490265d7db529596583ef1eff63a36b959ab96bee44ff653ab543276630b16db343a2c4a096f03523f6d3ea640ad7b72ef4e4c6a7367526240110653a14b0f722560f2f820bdbcd7ee0a535c49f8f22d762b34d2e27b8899fbb2c77ad6d3c0260aa326d140d8cc3d4cb93c5b82045099b7f01f436fcbbb2be66db24848a0dbb72af81b07f0a25d7c930b81d4ffcb719a9b7d7c638c38285e0a3f9c3963b2275471580ecaf1b3a1b023b9fcab8e976dc69ae7c51bd66c110441386cb3f1f61aba420f1d40d4e771f086b74ad8aa7d131c15298d622a4a279939796a1c2c51f03dc56ae7b70d997dc8b5289456f80d6555884fcd3acb4b7188c708eae45fe6b4d583eeec245140d918ba68d98519554eae70e32b0e7244986ed5abf3378b348b33689db232337569d5d33bebf6fd8fa2e472cc5c6a5f3fad627e952144811be37e13a713c9633ac30a0766aa61dda446c0cd7fc437555cafb3cb4ddf39a2f6f2af4b114541b89ad5adc9f4316a545ddb56280eed572bc52742c8a0afd348b037d0f954b315338bc1c758d2ebbaaae64df7555114d3c94cf38041cc8a41235aec1650214949bfa13b833e4b443bd4fb6bfbcbdb075e349fe1fabd5edff0270d5a211dcf10844f6bcc1a5090dbc5b08ac60ddc0ffbd22f8be4762b2ea7b60b4f32ced9af036e47a6b6e45c2f16bc0b141adbd00ae5579a99e14d859055cd772202763718de4f877a3d288b2b31b03c85d59f55132741b067c6d698fbf3d4dc6f9f592277cdfc7456666d0ce8ffcee233048eb425921bd0774ef3de5595471cd62ab8ac7c5833cb1489b632f571d511fa82d5f900d1125084980b6b2e7e27f399bcf95621525fb3976b318b909d37554d841a28dcf44ae6ba6a24fa349442dba1ba5314af1faee9593e1d92cb98270d5e6bb8f953c5193b456bc79af2190e3c97e90332cc5dc4198b41de61e148b07ce3783527b9641d0d46c8b553eebbd874d4f95cb75559a3224b374bae08fd35bc3eb111dd3574ce87b546126b62d461ba5a5bd5e415d95943e526c6cfd768dcf68cd4c9e999718cee8157fb49b3debb8560b20725c91dcc33939dd9c0cfdb3ccfd2c1ef327f392f49df6cae1adb4dd0a169479aecea8c37ba9b9b6d67a8e6f48b384243206b261bac9fbe76718ef19375d7b54fa542ca0a137795f9ef7282ff3c16ea41da50ba2478bd36964ff4841be8cc23b536fc5ec37ee022357556f2673edbc628b742dade1e471d08d5960641a81a6fd0306d72d7f72fc41c7e72ffb897fd5da0b55d7ffac53d8b80e1abb830e1cbe2f22695f7ca21678be7a54816df4e905d9de60c16b656b4850b7f5e713a56e4df8585f05493cada915ad8978f2d535edd368dbceee84469cc2ecdf6ca1d76b1d223f5493741b647632ce00db01fce01ed17acf55dc0d8c6e1570ae03bc0ba5f64dba530d4c1ee2682817edcebf4181338176502c2373e308c09f416ee65b2f50e4b734b9ecb9ff498cc4f3174fa01e2ba74acdfb3eb5ad87fab31f4ab7f210edac9360e7f97650b938493bdcb46f2034c414d04e78f15c84ce9db4df39f32743f933b37454736801c73455a52a02e8b04cfe5fc0225acd89963c547d150fb94ab00b8695b98c83adce7ff49408cf1992f639432a1b803f3119a7f173b307fd8857bc416f7b95ff60a9f6af1895036264d7d2ccc1d4521c88a3f8c328a7fdc1915a454c72fdd3f394420db0d0b3315f25f3d50f97826568185959db70e73fd0eacf5feb7f059d6360c64f9b0e499747561f5177ec82cba2ece137b53e60b2cb2ee6633bbcf62f5103ce56e7f12886bf1ba069aef854d8cc77a3866a5e263764c1e009836f03d6ea5cd7730da7ea2709527f4cddbc4994e5d0df16f3fbca49133b7bf7bdbdeedbecdf1713f362d30bf2bcc312a018f30f0f9a698153b7d81e2fc15be7e0c59f5e691b8a50c1f08eeb017fd453a79100fb6131a095461b7f189df4f61c99788f90cec5ed892933a27020ed838b6c494ba2121186641dab890251ac152ac4ad46a2d55ab8bef8febb775817bb1ffb1a6afc9ada6fbd9518a1cd159f99f622ef5bd1dbcdb96fa1e954e59e44d5579a98adef29d831bf61591eda973619092070e5d3363349d139d2674828398c7342fab29b08180f93971fbdffd559980c245eb8f4787fdd676a29cc9246b1fcf3520d5d943e100eab44c6a7e4739b96bc9532b9ceb6415d93640271e1ee7b095cf207f59c6047c51d915a4f10abf367ad5b0a5d3a682391c9644f9a164b8f9e9175b8c0017f239b59467d4ea72e9688770a1138c4c0ea510736a0bda8d302ec6af1c74cac491c80675c779df95206443d3d6c7b1d6ac24147eb43a5c3c2f54651c2da58a4e9e5c212fc5754e2820c41577a699c5bb5af131f82fec9f267e79ee8d48fc208af4f78ef143b2d594497177b743da73ebad6c9bb9ad946b7660d56a6576637d5256bf346795d3b2570b27102387ec140519273933aa001decf61617fec1600b2e03a63d655e1f82e9027bd4eceb6e282ecfb5c1b0c42b09e83ee66d672d8fd73c436d8e08d44c46af859073510330361799c411eca9b18af5c5e93b0e0609aeef22e7da7d4bba0803500352b90d6b8cdbd7d0fd2e46da600b96e79f512c55cf9b8e2f3a86b650201d5097b882d61eb373ac7723fcc29219438e324bdf838331344f781c786e1e809add4168a8c600bbc23078b08bceac052c8970cfbc2c2e8afa06065c23b8970050afd20dfbbf5518dc1faf40783c26d52c5ae0f33f5eb059dc01db38bb4916de850ad8eaac0d98dd75ae0c5beee889ca0a33a35547c4ac50089317cee4f81a479447dd4236c20a7c9fcea8da21724ca1f21c3f42e67f7b5b1f5bfceba12c56b3db680f43d428ffc375b6c3ffadb04a87f10f4203d8cf7e42341410a7d8d869268e7d6ec3b2102af1468ac9cdc76a5bee99454e262716522dd97d2a2a3c64d19f01f37532a2554971ecbb51c0cce1a2c529d8cd7e82c4136548196810926806a448e59b6f6f318b25eadbb3c04c64d8d23fdaf6999365ce6364aff023ee24d24687baab534f471803336e8009991e02114684d1d61cad138d51bdc08e94af7d0956b47fc5234b4b12f6940ba26fc5ef64683fb298f8e3af56ff56c066e7da6ab4efe5c3b03dbbb5121d42f1eecfe1d645a8be222fd393297098ce8b09584207e55f2c968f5bc5c0cf269e58905dca80e63ae984f8dc0ce5d9fed91f982eff8e5e6cc6fe844d0a49af9c3e6d2da7b7c135eaf20950799bf7502cf3006cd560cd755f1b0faa894def9a5ba05e18e8e5fcb2ba8e4c9dcd2904cfc145e576778649f4ddbd3a1afa1a41460336a927eed2b9d40f2bae74a054b3a5d6e4fcfc743be4a69e49691634df0cb4e6b5ccb40d88921fc75c9dd2691fcad5da4a62094900cd075d3d335b6bf0f365e83d0900e95834efa261ba129fa03111da25d2a02a74828565fe6505fe968ddbb116bcfca595d43222311b3a2259eb7510259d3834c175cf4f4af4e6a256d4952ef37c70a3c43957de3209d6081e670f959e0c1ef17b7d63ef340010bdbc056053e5f9b580b05520a8c0efa63faaad9cda3d2315d38e9f78cff2d31721279678853c452cc9c6cbf801428f7745aee023a57b61dddb9caf255031ffe124fba55e226b5b84ec609ff0ca74f1bf8e2034c4d7e1d606493abbaf70966aefb7666360660f540b8eddf287610da56f32e6f8318ba2a6d8a8f3b480d6f35a9fb9d91e18e3d62072433efc5d32e70d50395791fed5aa1ff2c331911d438ee1b9b49a6ae0e1b36c16e6a8413188d3121b8cf1587bf8c432f2139a51341671369baa83de21150800f93288bbbfee9772d0a8132bf0d498c73565f4288d0fc4bf47a78ab1b5e7f2cc70bd1cf0b92f6d5d73e9a0fab5cf47e771f4a1b8af2deb41679282f8521b0e91e4565e99f48ffc2c7cf34cf31a5838576045a3a73885d3b2f660bad2fe840303b2df8410d62a9e20dd66e6b8b3a4204a4a666901b540f3045ff8ef766019aa417101dc537ddc1c9ea511b12b4d482a851255956a1442770f9e217128b8ff4f1b6138766dbb223932bd8284deafb66acb1a29d82fbf58a3931ce7724629151ae6733481cfe050d1717f477378b0b97adad9bfa9c46bc7d3e6db91ba10ab6d177f7b8512d1fae011cb41bb93c2f844ae236c36a3bbc1893f0f5d2a11ee4010345a721da52bd2b2798ee99558f3e4b36a3887d67c4f4b8a404b2d1a3b911da23e006a1a4020cb36709f18a553c059e187bdc1778fa2e05aec2f25bcdadca6c0bd1ee69c564c0ae3bb22217bb46c9a486319ff3943f08d5a78153559477d601c7d857d14e8284966a2dcc07017b413b856a057eb3140178a4cc1b7d03a07926da832a1c31bea0610cfb2d954a6754f09818c0a72cabf6813ecc0472c969135a37d68e80263f98bb898bff5a7ae424e6ab6bfdbe06e66bd6d95a003b439ae8aadb1fff4565de086337dc17e486bb8f83dd5160aeded2280aad15df682c80ba74b78a15a53851d67dbb8285166960094519537ce16d94d34c4b76ccd3afb929c7a433cd60b36f19e7c219bf471ce9332fdb41753be45b6823b58a4e502319c4e46d897de5764cac210203b866bec981f69c896d97d67acff4eb6831b1d686f88cb9f664301db72cd6c3de02909e978f9014826f52a91a276db13194ae8e58f87c467db21dcc29a5a406570635da4ea6032ed7ba46a43e836ce88affb6fdb7afba70d0d24d9859bdb6999eabc32338d5810fd68ad3484fce3db744685c266aa8e195733e83f838a6b76baf65dba37fba87d9df3ca4bb13a30a4b12cf47f2610510ed095c5ca4b2e30bba3b194ed8abac7fb34dda99e7ca76145433c008d540c2ec53d7ff5929a7c78950a5e32c12dade04d575afcc6f7d7fa157e848dcf070d29e286e7fcd0518ef4dab208860c83e24e1db1579b36e6578b916cef674f32d1de98a6caf5ec7b6474d158913595574090f63319e7914872476a5bfbc7a0d6990593322a0bff6790ac20a5ef3ad5268231d654335022d82a3a341cc7e67abf81fef168416455eb711d443a3a16c53126356aea8db8caba7002bd5723c0eaf7a1cd4fde68a866e03252d7f61874a00babac5d789592aa97f35a596da3b62ee62d031b24e5faf2a2f59061994eb06f60f619e7491fcdc548e469a827dfea36fab95f6747d5d16facb689a4f4cc056b10d151f9e34391bbeb095ed8f8220161e5d330c20d0c61177d237a959c88f76093c7d051e73a29e151e833c8dcf0a83de42b03bedd2da3ee9373fa372ac73ce9d514d8dbf5dbb27a239fbe83d2c9a1ea821008a8275801231c3662fa41c23700399214401f38e5e8c919f6fb05eda15b148bb6fe748ad1eab588238cb6dc952e733ff912f6208cb9713fbbafeb32d50248867f7f7eb270b4ddcea98d0b31f42adb80616e06235b7f7b37dd3555cf96d625b702de112c210e455f200d97a55d7fdc29f89d6f80240a52ab1b699cf5ffcbce14b9b034b3ca00b00d773cfc912ab8743f5e934e6a7284601939ca817eb15184598577aeb75f1785e8dc09abe988f0ad44a7d23da93c5a8b029b546a8771abb609195e05457f75ed93da279f615de241453b62b293fccad9756ed6b63efe21acec5c2be4aa218b0a03b9c05f59d43e14ce1d4adf2195e5cfdcebefff89ea6de5aadb5c1c8e2fce4c764629454e7e9c57d191036a5a1c2a390b46c0d94e89ba3f87f98bc4fecea7eed311068f7ffe8cd708d59f1a9cf2ea4c279198e305f2a12c63e301f2ed979d110274b4db7b27daedc3d43d53c004f59074297f87f1b6d962fafa5f7853c7e89a8ef3f7024bdb9036c19286012d265229908052358ebb22bc5249607e41624991802b07c76cc0eb3d258e414697d378399c220cbe84df43e703ea9a245a12747ea2282e61465da21a5157a3c9cf0b3d9b7022c63d0a9ba373fe60bcffb58574d64ba4f020085ac1d1705e6f4ff8134eb5b8e4c5b43d756fefc6d155ff3bdf1ad2acd7c4dc482fadf4956927e32b67321e98b48fbfef2a00db3dee971d93147b37e5f5615c121ef823b9005114eae13ba16c5024281c78255e7c26091f63b25298ce3de7ddc4be39dfb3d42666b601eb7fa43b52b48ab1f4127644fbecf5810d3d1e24349c718d279d432c530d6e7199e989858413f1a77b17162ec0854c47d6bf8c479d33d1c126f57d75eb72a24ed53dd6276c5141ebd578dfbd12351e5feeee84ae7141542b42e7f733e8f1a5f45337f635aa55c27cd125badae63a6b499639c617b3c8ccdeef05a41cf6912ba33a78e1f4332cd3cca79f0cdb4e178eb94362610b11dc3620ab81ec50e50e176533fec2f60e6b8da24c5512240815f2dd2d9d1a9abc2d12fc7eaf35eca787ed37458efe6f25ca157d43d0c1d4f2dbd38df349ebc6d891c2a630e9c0c1a9f1e281b72b3bb3d5f4bfe0eb065ee9e4051b77b088c2694ed05a6776097c7952a5f252b5c482a655db435b00653e773a156f9f2c78b9a543c1a848da69d94c9172b4f6203c683b31bf559bd8e5c915a379b678e582c223e4b814e5d70f3b3217c069c18ed9ee9aa2c5205024a3aedaf18f5cfa3b5059ba150141b784971125ce243ed961a3c8f726a01f334cecbbef3cdff03784af5e716506b77ad571bb89ed47e00d895c8ad6f95e15fc5033be4029d1db9332deb2a44afb16f202617eb1d8d4a0fe1d95a74fdcc3b7c23ad6e900b4760be75e6e463bfc9f32c629ecd66f1970ec1efe7fd5fc4c0c7755a75046509e07aaaf4c19c157ef12a70c6a12e09956cd0514916e0b03e8f4b81be5c33bf8bead1e12eeb7f1b4eb125648aa482db50fa55cf2ab31a469a50662ec6ca01f0e8cc740f17ca9203c652d01547790a31d3c327604084c50b56b658e3eef2bfb6680acdde42465dc848235bfb05706ca79c0ae406e5cbb4bf32489c056958da02edfbb7f8de13191e0a1fb48430d19e3019222d1971f21ba6299997f1277254e4aa670c0b7147ffb57117ab581d513837309de971e89d157b1c1980c5e885957a2f639943b484a9085370bad121df78e7a1cfb9a9335ec0c62a7147d73c88fe8308de135aa19b436604379b9f07d566d1a42c1b0234af910cc6479238890d627d8cf25cee7b67b0f75e406062a0ffd359f5caf7ce2131ece0d335eba1069c623a2f0ab1dd916131632f4662250c09b7ae4dfd0db89751f6b1090a2f17c48744dc643e3e6905388bdcb8769bc07d7b66591703d2755353dbd37eecedc2209c5df5c269d13a1da758e5deb2779272f2a90eb822a47b74438abb1bc8db640b37f749292b641380ff66081b80caf41936da0aa166da48a94e673756b6229c43cab0e7821f89b645792509043b8d251f0cbccf608a518ffd02f8fe509c9dea4607d7b46575cea875d24406e641216087c7662b83958cd063584df07d2e0d1bad33ba6bc2cb1a1f930392ae4c3045d4365b9c9158002e837f12ec1456afa8769a06b15fa979cb831c58f424d657227df3184b1d838103f681def6abfd50fe6efe9c86d261519c8b23197f0bf25c7274d406affede9c6e22bf5d8aad0c194230579f303b0c2b5a71fcb92e133f464413a23bddff8ae6998cd30f950c09ffbeb26c13481c2cfb455547fccc50a43861121b3e164eeabc20d3cabc48d4e5aca5fff8623adcab8accbbfbf5cfb029e7a0d6c667bf204df24fa8d8c01ceaf85189e591c2f7498d31dbc5dcc7f36ba4c7f678c023667d6ac195a0f6e4ee38735099549ee70e2e20ddd6557b2349bfb361dd04ce0cb5978948475579701c896e2f4be79a49028a277ead1f013eaf704a56798d9eb29c931855262cf9479c33bcfa1b62b6ef3f365fb7ad09a207a766fb8cf89477f615b7d8472cac69f23da40450cf09ae1dabd7cf19c94472256613e6109255f061d9763b97ae0f400f1bf6ef377a95d160fbffb13dc44e7733f310128da2b525354495df0faff65cfcb81f9d732ef36ffda20478aec87aa5516908b01b2e41540723432b4810d529f34306adfc308d829aa4cfa494a1f019685ddd23dd3551badd89dfb76db4cb9f0ad5e018ccc19e57d8da306047c1fb2cd035d20706852028e9258f5bfe7427fe7d7ca7e5357ada2785163c3f0f13fa1e242f9c2bac8e6cc8a0db475c4aba91f34201a6bc791d24ceb4f725f058e7c0805ab6833ce0fff9ed65af9b5013ee08020fb47ad1dbd1d47e4b7adb7440fe51915ae38513a024d6ebbb699f6b083df1d4ff2d0f530d091436dca49577fc09c571ced288b77e3387ea5a4c474c36c913168b6733240347e4819310a75c6e77c49707180c3752cb16c5f435cfb7217fc2bbd144d69a57d13c1aec33a7cb50bbac7d42d6397064e963dcb11fbf0bb7764b49b267400e02ee20107b02627d409284e536d23fe744d3aad1dd86caf1ffa4b9f1981dce5ada4570ed7e7e6868da3e1e4811074bad4af0a7467cd95f16eecdb80e0f9d8d96089bf8f61bfb47ac3fa857ff3c826a06552a5bc52124704cd14380fac75f30cc3d4a43bd51d98341febba8ed16d9ae678887de3e63fee2fef9c8991a0edef246d1c21dfd1d19beccb885cc2e83c930b1679dd98709642f1fe24122fc937f07737c0ed9320720605cb5e1f66d3e1542e45510513c62e56cedcac9e65d3beac3f3d21a457a3171b9eac7de8222d813ea0522f5fae75216072c1a0f7b14e5361b2dc659e182d70ef6264dcc099b2bf7f3606e84ddb6411009e2aa9fae51abecf7e494798b3cfb1b5d7bd50c07a7fafd0a9da25033811b39371d46137e86365cfdba519084d0f274dd9c82b84723fb71c8ec0accb86660f0aa3b694ffa171a9ff4cadde743c74490c94772024ff7be899a62d0e64369c2692c3b3da98b4e4779da6cbfd5c89cb8951b659fbc7a29eee25c11ccfabda2da203811995786dfb6421320d3238208fe6091610fbaf74d65dde4d9473a8c6ee4ec42a0abd009af6ad4d9f93f99184962c50e539734d2ac1efa77877ff15cdf5b2b5dcd89b7b91921266e800e3a1461f86f4519240fd6d739bb8ba6e9d1f7517a3799f922f211db257827473196964929c0d9676df05f9236e8fdeca86015861ff275dbc2877808ab43997482ef6e7f434afa4dac155f27f04dba4ffc75b5a2a867f93271a5af51af905a80e304e21bac6cc5d787eebbe515c1391679cd17158dedb09cbb2cae78ab031ca0a4f1771b8605fb9ebd05728b4fe5ac2e8bad89961f51e71ba788c7901ae4bf1f86e814d7ad17f72c69fcbdd1cf50c3370895e83184d9816b4b5b67da4388cc1ed255c49d0ec545074467e6e3b394b2307ea0b0ce3dab1bc5f379959d1586d8b4f87bae3361485acc8cb838165d389b4356a2727dbc715bbb48e3074fa81a5890c57ad56556d4cfdfffcf700ed425ff43c8b8ed66150a6d4d47e392a9262aa3f824e9fd145d40de7ba6af647d55612aaa1e6987b3a5e3dbf864c628cd155451aa1f4b9096ebd2af1afcdb424feaa9015b756ce5e51c3b2a5e666356f1d51d574ce8808a9c62cbe17bf34a078cd1a2a7dc96aa07dde6c6463b9f34cb7e9ca2a5f62421b3191230fad4b08ce24674a6e1dd35653b5fc64da1e685e04ef0c41a516ef10fd47ecc3b68a41ce8a5b02d02f912ded96ed91ee5d47f28d58bcaea433590a2715dca83ff5cb26dd54a93b399d99b6a4521d967eab6f090985dc9b7124dd64dcfb39a3e81473af0b02877e274a1fcd7ec826988433e7c342312440b90860020ceffa3733ad874cf9158fe7fa459031d4568df88e0482782d79cc842921d642a5d9472e8f4a8e58241ff5995c7821a23feb665d14d1d899380d469406692fe1ddc3e072f39d63fbebfd288c1b8ac45bbdd34dfea4f98cc7de9f5463bb6dee3698216b60474e84d46631a0ffa03225d1560c686c7332445bf660d76c4a2e9e7bf4658bba59bd6d7234a6b8193884be97a5227b6776269fa73665b20b67dcef9eeb7ceeeae776ecdae134ccbb68adb66daa36a272c4157083b8d4567a981b3450da67b264eea06281cc40e4ccc176473659bf99d31a4e34e9085311dfaa60d79e843e44527143f1e838140a412af439190d7431c853077c492ed40bd79f913181c763870adfdd5bc13a3a93e9b293acbf6eecb53a8fa75b5a61501dc2050fe97f816a0c28ad33df94a373dbc4847a1229a9e950b90119af9ebd189388c5b7c0854d70cb36061e49cef4c7dc42cd0462fb2fed049fbe3a6602c15cd039a6cd6797bf56dea08fad6b769c14b29c9f52461d9b30bd76e64d6b95f58673ac0470233039302bee0d09e53d26c3a2e08525f7b27f88e89ec7f3463ffd22a61a13d2877928af84b3237da1ebc21c575cdfbbbba10d540087941fe463b9ab67b98aec5166e4fdd66e07eea796b31eba0b75ce899d38be535f86d3387d3411ff89ff21ccbcca5b1ab264324dd933ed5fdbc94b58edb46a270903187d37e331fca469e16e11faa668e576d23bf67e6c8d1c8e93bacf220d31d59cfd3e930e9cfcaa10c0e5ee279c098b9e63fdcefcc6e976df849bcd70d362d843522a8ad2d635b65f4280f847f6b6a50c3fb955f9b9699e074f10c7a8891dbe7e177dccc73ef5dfc9c93aef8056091299f550930131c443cb580ab955f365612103d60eb117b2a9ab622d41ca3c6f410f1f6529c93905e02250398ebcc9715e42159245fe58c4d80d90dc3e7803ed807bb279cc2d28ad057c89d281a304018a29bd4d4a0f34d91165faa3228cb2ecf9d86c34afb4edbef408ec1f82043fc0b6fa3585925c89c6d8874ceeabc62fe7148f19e7dbec1130225fa0418b69630abee50f32737d75544eb303c2318716a197bd281649c60ae6c7b9bdc822c305f2eb60b4718a5505a1a8e49535e704933103c87a7f260a559d078f832159402e158bf4cb3559936ef0074b3a64e183498b5ca9c6a40cecafba3a297ec49dc6f09166ca58cd12bb0261828f2935a03f4a8fda74b382d10dadfd5a142ce1328fccc73bdb901bf61ca9a129a6bd453e6703258eaf1a508352117d8d7aadb594b5ca64ca38f644bb7395b16ff8c20b445dfd60af9969899307d6117d895c8d7eee406b1ad0c20486ccfb86f8660e2ef6d58b9b0a4f63620974e5ec5d4cbb793a404f2bb756f3e91b817353e032df54e17868f2302149418880f6c93850876b5c4cce832b7de7121ba080c7a3a7c4ca33fbeccd02096aed1dda28c22a82b0fdd3248f4f3b845b301495e18fcf804f91edb3352b4c69ab7efbcfd5ca00b79e64506f664f8b83b96742f7c5d16fab833fe62767c2183b888362da399c6be4eb572387038387d97e7639b67ba4cbd496225df077e65e600d083d1aacdd2edcb264dd1a7d4107b3fcf19ca24d8feabb0cb4f3ec0cafe5edde3d6f7a610ccdd9d3a4b9192643f1528064e4e3b86b9c63eab46bbc330adac1d231a6421f6614db26d97691dd58cef2843b926ccdaf8c09211bf74019df914200561ffce3aded802ab8f1e495592c49ac43a37b9bed2e30cbb2afc7b56c435aecd00c8ac2e611c916a762f37d02e2da4c5a487fef396589266be65c95fe831ec4a4808280c60dbe6dbd25d23d287ad74712edcf0cf4852166cc59e32f09f29e89f92fe1a67e351df4ee33fd4bad9d0e92fc88b1e549e19808561ad8e9ab566615cf5f83bddc0109d526dbb0e4a05a07ef323e64a4fd53c9dc50841aed12b64b0e646fcbbfa66cd4e140435ef0d5453dbbbdfe074aa7b9a330e3a050ac0f54f0568563112b003de9231745471adccba3f600efa459fa86535559d9eeec301314650215a069592a76631c6266c0feee12654e5b84b9dd97eb347ae0a2193ce21e19eeeeed3e480c715a92fa8abb2d2088ac31e24ff3f1ed9cea2f56e7d9d2afac2fdf6c3c4671ad6f51fa010f82cf3249b63067395ae96c65a132154a6ccef1992773e217e297f2d4f53950745d97ac935e8119cbf2c19cddd81f958e677bc079fee124f96063d1c6badb110bfc213ce71782fc2ef45f37f0998524d4ff3fc6809fb71ab624a07abcd334cba0c0219ef7b502351a9a3367f3cdacbd9496ca0f178c7ac3e3a115bf8cd265d2aa28f346715bc9e64c0dfa84cd34b5621fe1b7a0abb0ecdd5113cbc87e86b6959bdbc6d76ef2402b2b43a42b056a08c714ed7c744495a97dadef08c916020d096551882a3b691402b6105528463d7f5a4c68b882dfbd243e129124e19017fa03af720e896e159466e4b03d81a191ed8d88c216efaa381364ad32aa6f4a2a7f111d802c8aff0c46be2e0e994e72bc5213cf097981b51c185d18fab78411a3db75fb5a869f381115b394ec0dcfb910f06204514f397e057ecf82ceb14d7ff0f7e321ed6934473cfeb57dffb13d0fe1025e0171287777a996957161d785dad08df3f6eff22b5af1d1b8e9ec9505fa18af827fd2482b192c183ab083875f4b9f993779fcd09d26329f72dab7576a131df4cf075ebe205430800137d1dee4fa2a509d5843f5f8e4516f573df99bae88c005d6ca5a1f90a8978a6289a7d39e4c503ecfd7533f4a71c735d983be28803ae67162b08694daebb55cd6cb4a3cfe6c07b96020698446ca0929a65465e83b8184c6003e592fd082a361225ff522c185526185b4d8cea6ab367b36bfaebfb00a6a9b23418578b6f3f63e049603e15e6d8c224c8a17741210ca2320635be876e0ae87f5dd836699d2b3d7ae5d638ed6ee38c095ef7ac6d659e3878d46a136979463c413907f48f72f9d42b0b80f021c019cc285982f86e7718a867a20739fbc0c269a45818260ea74440840ad8c086310d5a3ecff307321e22c191c8b967b504bb0c05863da4de7a138b7bee53b08402f76cd3afed4ea991586ad76799701920ef0dd11f813872e541f52b133c723ce6a03bd9bae9fbca228bba4c0559d5b9db4fcf5c45ab9aa8bb2b1730437ba9b63258b7b0be71adc0adc4815dd68cc2efaddd36e85d1d94cd6fbb4d5826815915a64168de7273db96172ec1627066ead67bdfb24e8e3b0487be15b4aecc96b4d12a8729444ebc3d5e664a1a5cde36e304c7f324626445d468a25353d89c78dd9605d75d2a920a732c485ad95c7d66424b9099db185c90740d59151d87eecd7047d4ccae1d0804209937ca167fdf4a776b90566d574cd455175e5590cb1f4a3d7b12ede4b2eebb2144361e3fa7106fb4f6bdde90735827f8ec80ad4ee3b51c3a4df7df54a63dae0406c3b7477d22b046a544db78af14bead9640a8da96faa6b028644cb070aa64b92b83a02b61c74dba91fa932310d71fde55e27fa4224258d528c9b705b92f576109f1325eefad35e540987fb4355936b8db972adb08b7c0fdcbb2030b1ebe6c6d0263099d5d754c7cf0d0ce42357d9ab0d1ae36622e1070328177c0f86aaa929adc0d5b8cac011b2e825698a10c54c816ff5c7004952bd17afbed6c9edd8f0344b59ac96df980c7ad2ddfc85311f62b99204463f93e525a967a81f54374de5a1dd136b0aa55927ac8f359c778e778ef5e0440aaba73f3d3fe53cdb3810313db2cbbbfe2acd53be59fa83e7f0315b05c82f927407229d46bc6b155c123d05bbe2fece662d6639a0ac221c6bfd03765d91d012376a9946082e4030705c6fea592e3571a32de7668c4773d1852f480d6198fb42155daaaa66e734256581792532c22e8d6264fa137f1d611f9228fbda6e8d595256d3bf4d07b9eb7184245f0c1e62b7d121e8024618ccd6114c176662b520e372723a8ca3af8ee3d09a5af98b11ae0d8ee9fec336ded0b34c284e89159ded817afea7e4c0db3b5a02f88b42e8d71fcdff685dc72d7d60f3490dc1440f69c351beb93aafe2129397e24c653f8b280b8b51da7110aa0d1a0ce02e1ad64475135c572f7b31dc8992523357a329d7aec312aab4aa11d705ae926c3edc511391eaca3d77de490930dd5e45e116871278fbd1f0adf12dddd851428d5281e509667c3c3a9df6193037a5b7aa3a148c8eb955662b3623525197f9bab4735e5e5d9f42a6d12ff7980d59682007dcc3e56885b6103aa6575830d48d59d690cc1bdde064ddad0c9c8978c2ac85e0db762b799db58a326fccc569b4ac28b4cf8630a6a198ae7470eb31c0c0df4a737b03fe806540b68fcd4b2383b7c58c09e88022de30a705f7b8f37b3e1952f118d278e32fe8287b86f529546a7222f7f0046157a0c75b4ecdb3fe06ce8e451fc6b3b86332fc2803952ceabfa177cdf42ccc1206cacfbdd53c40036a87456b1fded7f0a3ee9d9a6c63b2284b936daaf204b3bee7c1badfd7e30fd25b830cbd0a5b5e5eb08e390a3bc48f70916a124e37ac185c5f001035eb31321429b3331bfb2d2e242406b06214ed802e7435d515aeb1a8ae65fd7d93835b60921d4c7fb0547bf6b779eff52d91edf19d25462bda5f777818a2e12a3008003e6176ad069e8f441aa0df594dbb1ce535b3077306ea6896fc9e2e3db5b9f8b7715ca2dd1619fe87b6b39831d2d6588154bf5a74e9014c638889b287eb2694a9e59e9be2c68cf308e2ab3e976b5cc9fa8c2928e8bbe02a6d7955c78a2936f709b881a527618228c1bac6f8a3924497dfe5963ef0d0f8414d632272239176f192197899fc8fb2b89e42700341a6c9605ea6136e0f0d57cfab4d38e9a1c100189fec8e4a2163d184b3987df0e3a6c7a06fbb9a750c3f6d2b7b020469eb0cd1e551e07c0e5264033c42b576c17e43d8bc8627d6ad6688b64c31c757c56d8c7a9b51a71918076881bb15f4473c33b86b81a7825b713c7828637303e960e3dbbc02d55e188ee54890073a093f776e95bb1dfaa5c87344d5c94beb578b6aa08d7e07f761918e925b3140d1ad3d42a3e55585a281d5ef265636dc513c9f8cd20634b1f84a506bcdf28cda986e17767b8dbe06715898dd438966966aedd2e631027d567a1063d34d98ff8f7e5e1056647ea5f70bfa0957826e18762690f4400c7fa0d773b94ec3fbd36ee6a965fa6b1facf1e2ce595399d210ee1acf1a39e70da71467636bd71130b1d6f9dac279aaf08f41c2af7b2fcb5f29f47276b6d1c1ecad7726315c0fa11ba06c3739954a0fdaaf01cdaf1a44d17f22ff7eaee74b6c5a9892f45db1e4b759ecb57a37d49fd099ff990ed05b588b62f89b7c4dcb4318aca068d44bf2cab586625f52218968900ae4909093a7428e0113a3e7cb619ed91a636e4dd01d34d3f578bf04df507bedfec134a95d79aa3efbf51bed0c7678aa2747a2dea28a3e09f9fd7581cd7dc9d0be72944d52e2f0805931e43ae6f5beed1149f4521bdb73fb5583938de84ec9b1ea3a30a6c0afcd1bc158b78cf46c88756ffcce7fecda766782c7405b73bdb7d8f82ba0dbd51fb6e0cdf35e2bdab3b422ff82f54242ce295726145acc8aa6cf4ae8d0675cedd79b7259fa0ded1a41f246ae074b1107bc8b2b32bce46be3a14b0faab18170831258c968fc35fdc1d0cbee1270cd4c9b7963469c77747e3f8033b7a2c2c1a2dbc015a367fc7c9bd382a9568068b4243a420445b00345eb98195db705b1cbf78573bacf6f18f204ba7d895b9d7d25279613645639c2ff29c153ef43b5dcd6d3caaa36ea5c1328eea553bd14c0b6d13bc93c504a476542cbf9339c3a453a94f9c85746b31c7e5571e8eea72daaa7f9cf33c54abda5afae612ae7253dd1263cf8e2893b05f3a222a02919f94c461bf586bf4b130b727faca6e8f4159e661880e580f7cb72eafa04151c49f1943dcb0953caea1e5c012869dc684f9269a58032853a6e6b176ab534a2e194b119f035b559da9b7e33e3e0f876c8599ca8d875691bfbe6eca7309836cabf89fb6c7dabdaa83291ae465371c57f3f62f1bc478a1733f2eb29d75c9e21ad25db7afe48fb36e9327ca5d205e5cbe486a180f8bb655103ba0a60c6d2f643492d537546cebcc04874c96d8b995508e741ebac0a0de984b5a9c89dfef51aca21b669326951fcb8846028797ffeeb6db605a3095a0e0b7e95b945fd2054c47ec5f376cfd80b21cc35762a706169f4c58b3275db98c749bbedbec37b4a78c3b96ab9d3ab906fa495eb81c334a11b51329184c95d3cc93a1bc1605e33d792822397c9a6ddfffbf242b055577223bf0f6898a9cb486a0ae1fcbdabf42f0cfd1c46c966f846afbe43807aaa8db7c9a665710b4d2e08341df3cc64cfed09c2e6401b023547e2b962da1ebcec80c2ec9d4f3858c05a8cd790d01f0671061c51c1411bf05c9c696b79ed16258b47ceac72e36d8594cd7c5fc648f50059e5d14c9e1a7618e13cb7619f8a5cc3eda6a2cb13df575cae0e9f3b8b0dd448354d7d9253a404ff438e522c05a41e29913b1bbab3e09ccf206e987810551ae7e1ad0bc7a977c28790ff402cd649b9e08ce5ec0d05743bb6792f008b423cf22220d8dac96e6038cc60f21a324556e18f2686b9ae60b29231aca483cfd21b8207fff144df3942973eff01a224f5da38fe3dedc7cb8409a939a0060bb8d37b62eb66b8d032042c3496f529c18979b1a05eb9f29e84ecae10419794fb3df41562cdcf94fb9a5839e25ce80a77571d838752ad184c2cd0c23d5c85bac8ec78b7628a4a7ba0ad3aa61d2fca1bc2645e87b751c16a9abbec72b1b63d84fd95ae051d741b950f48cc25ee5f19f81a8800f56ccdd59ac8fd13250e29730151432c4228dc2102e7f1fe9e7915cfe45be17fa73425d2ad592b4feb4452f7befc7b644c189972425b580d5347ffb5bcdb2c038bb728d8bc0fd4db63c6f1e38fe50387c323ed6edb10f99b310aadd9685afe454e2f3d1813bae4e4b55b1c5d7a2c655a90dbebbe6781cbc1de25e6915f4fc9f633b799d3bf067055428bb0fa17f0a8cf73b3a4ac66c090fed031fe1afdac1ecf6e53d8cb79467e82a24c8c4c405fff845aa25d7106b55da1ffd13538cbd2aeb90e296c6e414db853ec4037b275d1c493fad4cfb47508f93bf19bb5685daf612df2c97df022deccd16566ddae8a0f9d6cb05b65bc85c9aaf6f0fce3b1b6340f3ab9d36c434c7f837140966990c9d61704c454574fc501238ce92bc1f4d0b499c77a622b4a6f6537635eec3b11cab85de34c14452b64c9f13c1ab34915a98b127b9b8b01e412ce9033ba5864c3b56beebb23550dfbde89202c88ba3e19fd79d50d837bd2bc1f6fff6a03b0e54527eb2723a608ea77f5aaab3238c61ef8f23fa6e9f55923dd9304061a6f186b5262c3dd85f91342a51f1b30586e623470b7e7e51df46cd05f79baeda2372981d4fe73a8c4cb02cfe90e7dad75fc0ec1ac7600d5425c1633368152a585bf3c676a7f0f44eb85fa5c8eaa830ba6bb2ec32493b8687a84c963ba8a16651a39420acfeb52b951e836ced4709b5cdc236c05f734349658cd5989ecb68e3e6ef88a119be072aee2067217252250edf81ffdde8045247ace7312ea6da2db7cf401457336d87e5321cf5ce6026ae5b6ebb2a6eaf5190958ff918a00ef99a8c8f5d0890d4544da73dc24b897dcd1a5a60158637f24c4c3ea08c6c96f69573b577cc56f8231f1337e004efd1963e255c709815f7c2315c70c20c4a8542fd88b62685a0dc01a8005fb3dc5f056cd3f103cf6e6a85fb6ad07d38723c8030597720d4a5c76c8c94b7a8fe3a7b3a5268ca3f09bc2c7c2bededfd0702348c94f5c67ef39750697758108a2ef7b2846a7bcd7239c40fa95dd30a79b10e30955656d88dc96ab215e7cf18454cf1be8e8e88ae5883f2b1d039762dc28f44cbe1c6eba457a0cb943b404d1102822f6f29efb9837448b46114bef947f768dad055960f627d23429c1e6540bbd52b9dd6bd8b87857ce4d0404e23c4ff7c0200b87404ab6ea694ef5bb3f5c41df9dcd43839c39e136d0647ca88fdd3805fb2b651ac94eb871baed651cf847b8e9782add5cfee4baf6430360b6545d2fbc21defaa0cea1954780f033d2b6bbc2880484eb1649d4c74d0310c6b641406b4d29cbc55cb5a71e66fd7d5b100083fdfcb090b3a3c0934b4ade58e0ceb3b4ffc8beab3d605e07566917a08c7fead9fbcf318140af2c09b667ff7518cb251aa57151c5725aadf7d839dd20cf0e6271182f85459509963fe2d72c37776d6fe09035df13685ba34ed1e200ff7e06ac8f9f7d6ed7f14f213779102738f54bbbedb606ed049bd94d8734f2fd7e223b5404d2df59083421484e6598e30f4bbf560d7f2538cb07fb4f428d8ec3e42a9a07f57da4986eba9e14816545ee7a688258bda64355437c7542260249d7a6a23dfecda70c6839291d03554654cb6c1d94cd37cc4f016bcf460a2c50cd318da50ac373ada26226bd2970a242a993ae65bd4ffde4fdd8b387380f729012a8d76806a43bd9943a892352d1f03942a2cca4d83a3cb2b1e67ab5ba9b262f160a6925208fecdfee42ca20c68bc19414cb22132cd7f72c8acc2d0490cadf778e1ff8989a027cb1d58b1a85a5a3fadbfd0ef774c4e5d743bc2d34d2b8ef19fc84b2958f170aeca5f4cad08de0f3fe72f169b89316b1ab6a30e8135ba92fc2ff39432f985fb521a2711f37697ea0bebb723c9709b3bbef561573517d288f496436935a565a9a6940ca2cc72e539ba7ecc501544c60208ab1f4fbcb2f67e7633ebfa52dc6351e7f10712e1f7a70a99da8d7b7df4f4982059ecd903f2cd24f4f1db6beda07878a68bd8f40773e1c7698e2ac1813aca7ce940a267fc63b1e963fa9f4189524c0d48e030d668c0b8f5efaf9579f8129e338042112bc0075ada5e7b67820a66e608e3882cb7b26f730803d8f214e74922c728941b5520a856c982b8d21be915fb538922d47ab0a6b76418b52121e702a82e723193c33a4b2a78bf9735427315a4a9b0ca481cdf0bab579d61f76a0f98b74af0690fa1395e71e0de4627bbfb99f07bce797d283ca888339fe26f95133366cbb9a4d15ac029a8989e3717ceffab8f3d54b4b7b219cfddc62c5b7a33f29b0b437369468c6e70b10fdc3ca28e5e8e17dddc466a21c025a539ded66843c4bb1d82a9c111016e286d1334abba9c723aabbc29aeaf3a3b5bb69e70e1bb066693eb45d8ad8fef7e243a2b9f30eecece9c12aae83f95e695905bb29bb1c8036477ee3dfda65887ffc223b5b6b10ff666cfe7851216ac221559254c7bebd2c076ae149a80a68ea58338d67ae5dd5543ec98f54c5ca6d5d1dfecc836103b4b2138e6add6f7e0984071d96082bd1fb19c6799c982a285105771b64aca12823156b4d287edb0128ac1ab1ea9751786d8a2c2672c06cd8697b6ed239f8ea888e8428dfa9800aacd0d1fdc4ba7633064756fcfe8947f7a121f4a3f3906f08c203d788eb535e1640be9b2bbd989ea2968aaaf516cb13d6e252fdcbe99c13fc65bb8ed68aafbc713b0059215af8390f9f5c81c900e47ae77d1b5939ca21ebab3e50ad7e082aa8b1888c1a2ed0401f747b5d1c2c7abf03a34d8ef32207efa5b642cc31aad18bc5760518907a0af72b5bc4866aa43a95e7eee621db13f3536ac101d740fd903248c21fb9eb293f1e949e9c0d4b039d4a12020e434532db220d3d59de0dc03f0d407349e3359279b9999f0e05213a23c88fcb684ed04ee95c08492e08ae126fcaf6db6a8bb907d4028eeb159885d26703ae17d15af2b453b1a809b833770135796f479edebb804a26763d32a8fb2029a5af8deff1a604148ab8811a547132e022b2fc4befc8bed3d72c6343a38be7ae32428b347f279581b3750d13dfa1bb13523bb646ce141af8a38310df48185db29f41909cc03737ea0bf8e2f54024949c7648ed8b343d885b303340303d6fc5c59bce5a6e0e5d99cbfcce1516356f53814bafb98b543c2040acceca835fecbc385b555ec61173a451e5216be286c889ca01ba7257b8328a9a77d439507af7e3ad8263f00e751afd20f7b0a48de36d31e6c5b0a6d7837b4b0efdf2b1b716840fcca1857037bf0669b6fb5fbe62ae862f183e389891888a6e5b9fc8ba5cabfdf90817708151a2d1d21ebf1747bb6d12baf82072f664133edaacf5afc2910ef15529b7a981cb4c6ef82b0862f8124dc7b9f59806a5d0dfe679b570900d7644edda5322a34eb05ff99974eedf8657ca9281dbbb2903d0a1b4205bae60e2d81bcc592b11aee2d9358b0d2e3b140c3d2bc3d5470f9c1b63c1e296d774239ca28cfcf2eb92175983fc5d0184689172444753360ed0ecb7719ef704b4877853cde03c6450e3101be87735da2d32ae451d70406b4aecf9f5ab6fdcde82194b44b01e0f60b869af0473f7462e784b8e9f2f387b59455014d7416c4080e35bf602c392f03fb59be3c9b09611bc4940775d3a44cc6c71568e80624a706acb237ba5aff4ff0d849d4dc85a2e7d9bf19c26bc2f272f7e895ea757fce8add0be33daa242127c875efc961e358be919651f9dea4fe0f9952a56b893444edfc61187eac1039dcb1039cf0c28b71dddae87fac8ea2d9943892a72844e4d32850a2761d2a6c1ba28eba7123f94fb14450e54b84d3fbaf40a486a7fc67cc498b4864e8c07079db484b96d88dc863a70180d549d87c70b27aed38d4efa3b20c97ee26bdedcfbf1481cd41beaba5a67531f5a4cf3035b39b1a70a47ec5526903418ee0549e3fb93573c493146b1b5fdaef499b07a3fd83280e68bb70afb63599fb5bd2778310e08e9ee449b971a76d809b02fef23dfb440436945b8945688c812d717960edbb0ed0117824427807871d244993fe848ca91ace727d7228e43f165af991d6faec1046cdac21bda6f8cc0378fece87a211acfa22317e988332d7c7f4cf30f60734fd6d2de005d1e451ac94adfb99d34b10e419be205ad6f46afc3ce32def8bdb9034df24cc2721cdacb96f8f3cf3c655da9f31945ed1435ca4fe2e86ae722d00125bd65b492543eb823fd9ff6d464bb967cf4fe392777f6d75d2aefbd859800e92d492ca6ccb425327e3890bec9921235803a22947b55e828b1f99d8d6527da83732bcfb5b4cf2fdb9af994531a2ebd5ff547da0276f3b7fc5660925bd669a88e8b5d23d70fc9306b0d5df112f97194f9a46a10a53a07894d9bbce6a3e426952a166868b12eac580685536cea7387ea81b2d9289e3cbda43e7da2e5120eb856b0cd46469508b48b50e091066011a574b8da5d8eb92c8fa087336a5edbf45491b2eecd6ba03ff80d7090d417f1c8379509f76edd67b0ba69ab550f341915c648dbe1a7bd590dcf46caf95d856d7cab56a00bf951b2738c4fad4f43b1570ddd433229fa5f708301b799d4513549dc30feb33e24166572f790e3a56bf689618e84631d77e0d5bfd3e644a77cd5e96aab63fd1fd8a416ffe8aaf7d765e5d4bfef5d88dea1648b79a4ffb58666d5df0dc3b03a0b772473afb065f14d82121301b333115b7a27f4ff26696ca131a0cf7882242c7445739dce3d825509c2df9a13923d049c2a313c173b0e11141c0b3cfc8992767935f72b0349ef1ed529836c3a61b28a591b7b559eda7d4367d440a45e93718e1f6ea7de312f493c112e0b988325653468ad10003ad036e8d3b3db695b37ef225ead72f1b678d42b274f7fe13148e491d34bfcc798c9e28f1af690d0515029097b50cfea30b3b119aedcc3cfa5646039939b69b9c1f1c7696a9fa7453e925ac28f02e85cf205fc123073227e989f97b2eecd7ba2996a637ce8a30ee33d1208a942a6f6fa08f2129f10e276a0aba55dc694cc123ea705aa63a1d52837fd002578ea9ef8a31f7a15236d30069b1c43434c2c21ffae0b5998a198de3ce5ca6826ce28b479f78544f7493812a717ba083a0b0f917e49bb5fbe5298cab2b5483787f5f3093585ddcce864e9f4ec4c1b5fd7d8c6d03963cc3b7945985bab7585612127847d70bcacfe3ce5f208ccb47f92ae8de880464a5b0522b337796ca690b4ef050e3610f663fa37c7ec82945ac77539536746576b65b11c5f147a968be13fbd9f7e5ebaa4cd96518af157a98b2e2c46afd35defe6e09939cef28d9fdbfb8bb0a11d9ef386f95183784f40f0509d485caf0afeedae845c0c27ca7fa7c6e697b282ee7a1df0ff57dc4144693d0741812fafdeb98653ace569688e87bf327abb33f4ce8199214115163fefc24c1f5f16e177131ca3b786906aa41c52d075741f8206e214dd490738c7e0b595bf07cc79bab435113531f04b330fac7b8494cfdcfee89607e8efe2f64c4c0c95b0287ef2bc6cdd8b8684a3bbb5d325c79453a4fc0d47948af15f2e2a45822d58c72e69a354494880caec46766473f6c266fac17163b302c0d62f2a5b83c29257adf498498269d26ddf5886aa7f151e2325bd19d012a4709f123c5f4c969ff9e0d9a038c6ef0a0355985d4da211c6c0cb3137f5a166bcd755143ef8f848b3f72815986608650a18e09672220fe6f41a7d60adcce997a76ec7b874c1b39f4fe3a48cc57013beb02b97da78043a7eadd4a7c2e182f77e8b43dc245ea90dcab77d1c62c7fef787786ddbc6b635a0eba67d5a138836fb015f7d438764540498d45b943dcdc7941755f1b24637c535c983f3c23a37d0cac9d32a133e2159ee09bc723cc2988fb925dca76abb5da71ab4d72ccf2293f32af24a39f5127dfe28a3ac816fe4424a90ef1ef278e5340f55fdb1780c47ac45343ed32c481a776e917d9def6f5f849f23cacd37afec312afbcee390d6be16a91f7638808126fbd568a20a1239be1943a317491c318fbeb87dadff16875a2c426a1e7cdc4d3d845297a2953923aa5a1911d5ecadbe2ac0719ba81a59fbfa01de899db518e0c24d8dbf7c31cf7254161417d422bdb8aed515280e56dfe2080372160197aa0a769ba1c59f485f07cc9c32f9ec148c2560fd5fa0946b3c0ff7b875ab340f8ed2aacf9d3e44833704699b0b99ef03a06523a8ddfb7c4e96ca10895c9d36682a845c4e4052a3bc5e13ea3c413c7daef7aed6ee870d830168419d9bbd33385ae8c1f1eed456ebd2018d78995ec57f37e1b38e01ffb6935c888755ab1ce0f85cfe44c3f627c2c2be8a902606061d302ea39807107439907642f94b7cc388ae3fd22b01767a38cb84b2dcacc4cb1ff5004c8889ee12ba7ed15fe001680a28d3f58fa8c0c33cebcefe582802fc802354d3ae9ce4c7bda85d9c0a480d83ebccd5d3fa37c1d6887533eeb25e0448033269be0f1278f4e3bcc4f7cd3ebb040c615e86d1a9108abe6c146279b30bb36268fd2ea55b9e11f05fcc70ecb2ebc98053bfac7367c0beb4635b7f3d1051c8b4d84268fc0a88eb3e2076456203027887119718d79e3c4ef6d4d7cd97574fe8fa14806858f854b6d079fd8d0f0d43cf8804263f8c4cab65053fc257b98a764eb748e420a5a1c66aa8829020eb61fa98ae4a50bd8fa331b9e2c442c966b0bee6e835f04f0373fe03b28e9d87a8b64e03a818ec4b06229eb65f68dea0af691c62180e4a656e26b3aaf8af855c137f70035422731a9821cf945f89e161099f1a58ca1c805bebcc4a08a2b248d08ef78d089a79855ba077767f557010289674a6b492952fa6a9f8e83a1ec42f51fa535ceca382798779cbb7bd8eb978028dddf692405ce8ea656d35180e145433f277b0168fec2caeddf5eb79ac5581b092a1de70c6dfa7f5088d13f2aa61e895f443e934bf32f1f20d4e81e3918228cf1a552a17744eeb8dd391b60fac8f4ec5da05f66f217472d3764bc8b8904c019fc2ec7c6f298cd8bc5ca9251f5b79d8234d1c0540ace85bc0900c2f1aa0d2c8d1b2635b9ec944a7761ac226054d00a74612981a5df66401098e68b3ee11dfe6fbbadf48365a253da587a297c0b76f9996c8649b93bcef3a970093dc665b47bf30c31d1ff48e5ced12fe3548fdb73e60793b2a25e6b5c58ed4046a11d28e6ce028d6addc66aa9cd2b3f297cda91777f747e7ca936b737c912c3a82b8fd7924b839cc17ed89035ea8a0d86f1fde7c89223162579f9e1038dae2d173a84f32107c86db40897dcd4c1ad905f465caf6efd4aeb635d973ec153e82b09868c4b110949a056fcc5fb211088a7a60a49619b9bf4be53d3d33704eafebfb96efc7b9b4c5e271d75bf9da927970a5ecfef8db2692fc457ff99e5472ad4c872b7d573f17425c41ebad18b696734eee6af99e7e8ba9f752cedf0f1b0617c39c7122c62cc9b60b8166b9822b1dbf4a6ab27d267f70997577b6793ea91c7b9bb4ce6d62962bc4e0cdf36ec225820da4faa89ba75d3c08214a141ca9cd61b6fde11ad3cf2a0ba24cf982a6f79a3e6faefdb6a76652e20b0ac79c048fbe67a49b48c58e75533292f227ddb072821bbb29afd86381414e2f3fee2600f274bad943a891c3e036a38cc8c52e0bed6053c42b77b778984b7e2c7c8e7e0e22002df334d2d6a7426ef86e1f935f5b917d51613116180e0d6e816d3749ceb6ba2b11eae62ac1d14cacc5f4e690c69eb5b985745f2cf757f9921347f3a963bcae016a13f3fb32e224b69a67d09a7f787887c43047eab9aee924f17ed28aa92844559d09a39596b3315438987c34aca27d4ee5fcb08effedb71d87e88e61e0305abc6c033ba772f00c7c154813eb2ede4c39badbbca846915da717d05f4acbf5885491c768f8fea1d54b7a743dff3fdc1f310ac42faf0a3ae4bdc9f7d0da3cdfbb3cc9ec8859710d2da34a919787978daf42d662c2302cd0c5a781ac9e240a323029e78cf78c5fbed8c1e890fa4bc867b734be258584ee865de5e4ee201fd9bc664f20ef87c96e119c5933ea81ecd42ee77e2a39bf8110d5f597278025b5d639cc157f0fd1d299155dbf64b017fa97c46ae4bf43b83f942e274c8eaa11ca1db0a890cd9cc3ee5c2d37d52af59d7f1d5a0fb0b720f4c3d017a32ab20c7e3b6d9a797831b37463fb4dd8f14dc70860ab809f558728518a038fd06b9afe0102a25a46bee82531382d297b844c3ce2048a4a3a578c55afa9068fd231320d17e7b935a6ff40daef5fb2c30217a0c7e328e60dd65b9965300aa5181f14153358e8aa34acd4d0cf2375ade76c5e06b8ef55e3b14882188ebe5acfe9e12c1096e41ed822967d230d66151112b791bcff70c7a5fbe0bdbca7eb11a5c0ae6218b4100e557f2f1f9c9da157e06dff9b614ca5df616b98f1f43f3a0a70357c02c1d5137b1cb9168a3ab03d0648edd4cbc3af7d7a709f8d02f4e42dbb5b43ebc7cfe05c348e820128b798dd5e7824e2259267901514f0b304b56a3abffd9c58cf7ae9512404fe4cd41121fb2d9a4ea2dcfafcaf0d0dd7aae1aba2402e7cf69cd80a2545ae063b74b29b9a0ab5bbd26f8f56f6f871b7e0fe179c234a3d483c8e9dfb179b8bd7cea4cfe786cf8a8b25318a69ec823074580f14b799375ef7045b32a023a44868782fbfc870c600433d0987ba8afff28e32de26a8e4c1f84a1b28caa0d8845e71dbd926fa46c84070a4540d1226f0911d15ee72ea95eb0afca9f6f66bcbdc46283fa18973390dc32b835c7a1776a424720e8f2a3814813cb35698485fa0474a9cb006e3a986778b44e1be14b2f56c743b52203e7876aca42d90395cc00abdf85705e79e6c547e494206cc6feed4c9784232983ef843a1948acb60b262761acb8c0cb02f65c1788b4172867913d7ca586b2c1d3cfa514402405febce97e2d2ba22395bf7bedcedfc76eea1f504fc82ee1f5f641130695d3ffd1f345eb2222c2256b81dc265087dc1bebc589b708b4a914b969159c709182b0288e6d7e5355b8af1d993199e301fa7399a7835938e0606dbaa1a7dba49abccab09303b7d2b9fd0c512549eef157119d1f05d144556ee0c39f3933f93a37e5cafbb3e5a257b8304b24cd9bb185bc9ed42629723d076a3f1965c68130cf1bef43b4b444b062d446e3df1363829a782312a94c3bc0412990c5456c1cc915bf2f702155d6087353fbd119caac31c322a154f0ce8e93c863f1e196eb34776694b0fdb42242ee5fbd38055ab5eaeade4b1376f91c2ac43770f727fd8ab543343bcdada64561ed43cdedee62bd6a5a349ecd7eb04004b5989b3ca9ff754fcfd4a479e7d8bd2816657beb6ffa0b8233ed7a4cdf3964ff1f7482309a2c5d0cb0ddc5716c0e92ded2b4e2d2c41a8bb77f73f5d7866d1edd932992137a5a3f606a5dce61d5308831c4d845846d6519649d359340e5f8e4e777f2c96c9bd0e866e704059ca445276a24bb05c2e9da9cc160b4c20189281039d77ff637dc5ee74be5f60a6d82405ec1d84f2d130238c7f88c5dc248c9201f02ff98a6f3ffbfc9a4f3cf49db66c9c12e32fbd07c53b3556a7617b3eb8137db6c9e76952e0f4ed281808983db26ff4990d802bfcddf4fefdb5efe535bd2a5d7459dc1c1d8a9c96f2d1130f3b44bd22fa2b0ccf6d34446dc249bc3f86158d9724d82cbef48e2e792b294096f46fb583d29a8718bf361e9ba5a3476951b3be0c8358b79c54ed4c53fda8a0097776db7584354189bede93511e424b88649aaea05eca07e197ec7a8ed9f890a77bfa8591c2434316a33e11a18efd38fa4ce118eebf653670a0379e67aafbfc5bb8665f00c40c69f8ed9f588225e1fab58031676710a901593458442ea34e58d2d276b4728dd5f0a78b434eb903c258eff1f6b0338915ebe93c5d5d3fd7e9be2d5824660b59573c742177e3a95d56f65bdb446350213f9ae3d8f082f88817ab0385848cddd80cfb1bf689976bb12a26006d8e4450179f9324725a1e93984c3b588d6c6f9d6dc996072b21730e1a52c1db4178cdadc379436c5adf599ae143f33a788d2d0c5a2ce8f79d24d8fe50a16b1845ee2910a6182f7c5ad25759f38ae4f0df76c4d27e53851ae947be1136494eea5cdf98cfe326cf04f5e4f25caf737a4fc5f8c01972a843de0dc286851d460135970a14dc5a1a71cde61f8f0d043f52e88f001f16cd3b26978d9acebc135618a1e8bc310ed6fd801120735a67416e918c724c794c0790195c858d889f37afd8dcc9f425d5f769de7c3f83c063a10708f441ee9568570567f77b2b2e24f4ee9bee912e94e08a00f237243a9abf26ac1e6415db1a1d0ecb0af2853cfc9845b03f5929a83d1aaa5ff126f21ba1b3559cb8a49817f0cce1db09308b2653e3bcf8494bb062d758cbf674d221bd6bc08872c83198895cf6ef0928e6cf7db66c070ec41ceede14526961ca76d16a65d9dffc64e3bf3c6570d54bbecbf4058e7bf0aa638e815bf387376db763ea4eca72a7ccd8a4338579e2ef4b96a9d52e88cd72de3e9579a59bbde0ef6da18550ae53963a992ba3fddf74f40bacc175f3ac65b4ada65c9b28351a04e8ba99b1c408ff66a7b7c28fc5e009450116a2151b7d4d7d6ad918f4a7a54113c583ab67f972182ab9bad092865c695b78d4050cc753fb5f013f1130af726aac3ed1994c65a9d760e0f972754ea26153d6ebef0172dedec74ab79c402036736738485fd12e28f4a6e51fc50d1d035da8c652041eb978e889fb63d26e053464d651e1d4f461efce5104e76172546ce8a5aacd43ec14dc5020c1efe84934bffa5cf5ac3b230c4e2d23db77117f351fef330bc9e8f1f9a25a1dd1527d35fe6737858b8ace1736259c0e58a7372131a491b16277031e1fcdbd57a03df43ac1141e677dca92f2975e1506b6919936f0b71f5c04ade8d757458b22e58393ddd19991a3e9bf1421d35747831ee2766edad853f910bd6364a7188de65bf6cd58c57b5150fd8082a76770ba9c64f8ddcd52f2253bb08faf3a77098ef7fd460b260b289481dc2745cb0de4b25d716c59164dd857ce32b038f7eb7633bf161e79b9ce401b01d56d7e5b2227d6e95b7b89a7ac40fdfbd8c6450f7b9fc1e9ef484fb83a9debf47c117204069ac251e8565d96f542c650b5877c314d515bd6c24a3fc1b8d1bc7126e6be7c74353f724ec33c038f92f5a448881c36a6d88b1d1013b26aaa9540ee0e484dd150ae7635e8c5428d04f0ec7a9f87258cfff06e23250f1be6912f634561c3765c8fce7cee8569ffea8453c99b3fa0356bc91a25ad67feac42308b726b11b004fbe6dbdfca094325371a9780996dc27f356ce74824cdcf7756da7c2cb5747dd86d89af5426cd500025353fd8c920fdd959854ca6da6a5c57193c1ff4bfd6f57b0614c5973b2dc4b0324da02755a9c8662492798dc0900c88776bf97433768a1c5db780d620fd91a4f53bfc55ffc6fe39017213f7019bd136b6bd5aeaedeba2de5a41a1ee69702c40b80c86fd6ddb23ccec269750bd4abdab763bec09050863ac910c88b581e80c902d8710a3533d4790d06097dadcf02bcbd5b9e90bbb3971558ad791069a6c5f6a6bcb165c1b402e800e81e3a7bf6cd0d53c4e6bbc054d82766230f3a491cdacfd9f643c0bf95fabddbba610cdd4882636b324318bd284428d76c558ce8d4efb4d8a621998ebbdbfa620fdc6b4b50aa6797e764ae71ad95aa9c4dc2dc5ac37ad37501beca0f03dcdb0aef391823fafa2a34598b7c2f06f43d3220250eee5c08f9d316fbfa9b9b7a87be5fab912bb2addac370346456e634244e949447acd560a8cb5fb433c26c60d37c970104a06e40a9bb853a84c7a8e86ce628c1e5aae0a3ed0d279ae8ae4dc1346de8ef8d45cacbfbbed9cc93d9fac9402788e6fccecee027252226954c608e868fc1279dee12e5ccc281a1a888e7e0dd0f2ef54b90c4c734eebf1d75d6fc39740b262c2a649362f14c3c8ce105b976de06e68b884ead70f0f111f2f044960d6d779701430d75f9870a4e3872143ef55858fec7c5958c9f08816306abb0cbd350799db2786d15f2a25231bfcd223c98bcc46af80f9b425c5edfac2315b63a1cc4b4ef44833438cf185c27ba264cd4d498b61239272d2ec5f25a8e7c7ec7586543b6cd279eac0c0154851baba3c047a3ef9980116ba7474d39b034bf548d4441365fde9bfe14353f89289b779b25b8434f61fd7f4fae2154906d311ff0247aba22a5f064d01766c2ac6902c8441068af81ea9510dc63a826f272275bed20e728b6a747d303e2bb419b3b5f560825267f8a722d7b2e1ac22de4a9634e6fe84cbd27c86bf8e1b8c36c1e4d2d1e29baa9b260fbff02cfb89f889fb3134e4eda45b98d8b47408d7402a6d03e5499c84b65a349aabcf30f585c2046e29af824d66a36f35885a9dd5a2a14c7276dce6dac681d824ea571f90bc86fd0d6b583480ea1a43bf8ae1c7a8241e1d89cca6328947b5e90c771ad8c83f3bfeeebc5a270f51e9c1a514a904225e35084b450330d9bf11c6b6f941e056569b282af714163b92354ede6f27f7d915ae7e1775f8c8603ee97e367ecb5b73d19cf4926fe37ea792f6191b5ce2c57ac4dd9cd327f48d49849be12e7e68651005f2dbd0c8983a894f6767a3503d22c772817939054a230bdd286988ecfe78e58710668e5964ef4e159aba16cd95f5de4ed496497fb9cea8688141bd3b3a996e96986831d8c9eaced563b294e35eb09c90c8ba55c214461d1e8c3bc1c5bfa8051990394a66f8d7b57494f7e35b86bb633cb130c9ccee0b81bd84534c5cdd50d4ee06ecc4c8801a10d8ea1c8baaeb48fe3dbc83345f39dd6bcce450b070e7ff1afbeaf4d12bf3434f6fdf1a3c97b6bbaf2d48a9ac669ce89e33223cddc74f6a7fba4c0c882898fb62656025dac8752dc2e3811e8154ef29ea4c1e165ce6b1aa0f2b8dcec109adc9d68929ebb059a73916a480a44ece538e2d8655ae36a731c8f5bf995878ae82e101919bd4237c3449d22a20a7d65cff9ff94c095a42b91d3006ff1cf529c72984a48c5596d59d498ae2bee1ddaf2b043019cd5806970876f6aa399e53db82b937954c3d9d611e53f03fe1183fe05eb85c1184fa6cf03315f76d446e309a3cc545ef03884e12fbee1b81f1581028a5bc348617876fb5e2a1a84e67bd68bd9adfb8ac03e0202ba6ed88968a6bfacb1b7d8467bfeb681d46097e8de46cd0cb68196552113ba22fdf3b42f9e139ad172db21ae538ea06bda2edbc030173069cf5765da87f7fdb081ea3510bb5ca1a8d1d737fa7bd3db590a0c92239e55eb8c5cab208aa486a4ee529061ea5dcf497265b5a3d02e9c9e37fecc574430ca8ba2333e2bcdf35ac479a2f68ac9fd81a6aa257e21dd5f9e8b0328bc503fa7a0b6b9efb436c10dc33e3decb1d8a025c72471baad40847324cb2de15509c809c269fb6eef0d34f08c8ce5877c726729d76a1da5b1f074729b7fb4cb0e6abe13fd6c43c5b1e7c2a38717bc8757824316f47b6fcd305a6a7c2ebde08f963cbf729decb72f498f3b1488e35f8ba20d17852f9be23cd217a6959f607d3ff90f4261153e398217d45afb53f0fb8cee2f487a69bc01a0abc77dfa3fae825dfff493b0899b7a905de404eb3d2c657384883ab226470843be48eab03f114d34fbc67bfe05080a93bb71f4fbca05157e7273a9c020a47247afbbde99ce3fb13a11a01c3ffe951fca95d53389ac2ee87336421347c5b9aee1eecccc6760c4a220a0cbffaa641453a16d087b942b573ac921ae716d23c43d885782a85ed406d16930e45cb01683b44c44c5453ca3d66b6d37b5c5dd7eb48b7f2fbd93133431b794511b89151f268834194dfd60b7729962795597eab26d9eff9329560c60184dab18eb7a9d68857babb5090896509c5d7ecd4f44ca947ea6dc6846e8356ad07c403e13fe3dfdc8844263eb6bbce3f84b69feef1b95ea4226126702261555e2be96ff2f879c7e9cea3006f2a2106fae84fc7f614b3b718273e79120cea179d6d7e341d904767363e709dc4517ebb68a51e37a041bac2a2e54a2b0202504968fc40cd9f2cd9934afb3f37181877e52fe34e720d761fbaf906d9d68cc33a88896eaf3ec1d5b52a8a1e2893bab7718a666afcb1f6a7624c33d3d4e7a518718dcfc7a02d8d63ff5ac8bf5b2cdd85c0abc9fadd27590a746734455d28dfe31c8eaa7190a4e5fd9f691ef70cae7c31fd2b7010050c1d4a79822a0fd6a69dbcaf80b1aa618dc02cc3834a62512ac59a1c8134992aef5a60e1ca5f937ea5beff37fe673378b1fd3c670c73c7ea70b7b826232ce022497f31b514834ce3e41e66add90ce2555f609476be3817a4ff9dcd8f8307047980812740ae150ef7b9ccf072f95ded8d61b95bd441cf3f3001fde981f297663fd6d379f36d5ad53685bacbf176363baca302c377bc9cf497257a5bcc29c4c9a334d8c1867f39fafc913791a4eefdd14873677fe9a422e181c4de3a9840fbd7b0ac52744ed564921a21bc998b6b2011183966b2cca17bd92aa46ca5914d0d2b9289eff4d8feb8ed00655d5d38dabe669f87f7884506c871bd6377d720655b4004b4e25ffc34f82508bf95a358e884216d333fa713e25134377b745db02c57caee5054927a1ea41ff26a8aca871fee4df51c43f633298e1115c7a7e49f5e7ed791ee8b3c11f557321e828a094c2badcbca3156b889eefb4ce813521149dae07105e021bf368991acbf9e3b796f0253ae5841143c725e349d5ae3bcfbf4e6242a16656c5e997d8c05e383b89c8cf04a8aac62b81cff29fb0d4d511a473e6f299613b04f4047ccc9ce904aa2b04eb1af16d64cc6932afd2ee6aa9b2a014c09e18d612a17e69c76f4360903dae8bb3f587760b75adb8019f29cee7c8a2eeffa2c1464c472c9f28a45db45b763c1fa8211832fc46f8acc2e165666d9d6e8d9ae2ba2141385e9c44c816811878f60cba625e127d4af620c174abeba5b3be7092e2aedd905bd41cfea77ff1822a29b6476a059f2c3d51a76f892bc244dbbd0a3df294ac4346acd60c12555e02b7b598b583ae81da0a40dfef398e436b120507dc0e6b56da9791e3344dbc53e049bc343c8866b6de6fd4f68e50fa7c8615a9c58a6f35284aecf5a57f8a091d8d782c6cdbb458db07bd9313681f1fedfd1cdbf0c0dd47026f8f3b7d72d163a1fe8093d2a6dab4a6151cafce0a0d8a001faec156bc01fc8816f906b5fd50f0ebcff1e75419c2482016b877ab8f79316b33c488af8bc9b03e576e59b917021edf74e12ce6bace29864add82e54195948bed00938586447ae8631c557229c667bc92cb4bfb25bd52c83969f13089e56359b06d7131cee669bd6b9dcd57c3cfef443dac4b597f0d1c9efbcfde2560eb124da011f298e26a5910844b60f055b2752a78e49adb3fe94770c64979769cec618d5700f0f2b389810b4c19a41b3f203277e270ca33e915d29c12586b0ed69d597bbee7a62da3fac5c9df7a2a0cbec5b6e9d83ab354c2ed30bd3c6a9d6a13af55db1f5f4ba566497c90a6752ebe9ca422127c086bd99a1aead9d488c0ba09af3e2b1bc5c63106cbab88d0bc44bf8ff3c67dbcea8c6ff845f8367d3848de235ef61b25c40b69916a6eeee805ef36569d32e0e394216bd03e22f4f799b42b3506ab7bffc38c29e04b8683815a0ea052d62be7616a2772815bf89475d8c18e67a4153dfc298f92ee13f258466a4561797ac984ad64f17cce41ebec79440a0d36b2649a817ce33da61534dff51675aa3a0572e67a54bd7b62a6caaffea93a44258c414de9ba27ee39fe2a7dd3d9fe0dbb43e56889673670cf4fe7ac455f726416ff6740db8bb7a2359e2b3a7928ac0f4ec00c1a04f0e41fb3f47fa7c25d4d8dc28fdaf836185b24094b1ec1c56697aea604dcb8cf43e275a55eb2f35c8e3f668c2355f37bc9c91e5febe65bf01c56a7a10ff9b98abe0df9ee05f2e35943748e9608eb365997dd99b41100d604fa08402b6cf23bcf4c117038b52c4525d6293a6060f165ecbe3b3c0a63ff3b38b511bc788c0d296bc4eb680a69371bf17c17d0c2b7ab8ac5c9de7c9da03ffa361bc7227cf846b5488749bdaac70734ec90d26e465bf9eb3efe809f37d73e65fc1da064cf34c2374e92bf2fd9aabc6a1399948a7451dcec8ea0f984502e748414a76e1df3c48865a46b0abe8ca2e010708315f2f5016b375b7af76b62d4780adb31446a8949f8d3b7b22154b6bc16fcf3d4e7c3f7cdb00719271996fac525069b6666ec787742a73d7017ea4955c820e9ebfb4e77c7591518beb3b706c364d268681b8a5b6f1adafe186323ef5a19867e2a25b33d5438486e0f734bc5801a103ca37c10df20b679736cbb1c16f17559cd56b26da597919580eacc59b443191e5cea5a3c55c754081d8c1d0fa4e6aed6ffc8f82f14b3fcf5c61908b2c2540c571732e19a42bae4994efa4386d980e47dcb3af56df49253eff6c8535699371cb7b49f7cab0f1b8f5e3e2de314861e75a446709716237d0ee57cd0bd961e3c7700cdcb00aa799e14cb88d19d598af09ebca69a852867cdcf60e8a59ee90941a7922ec6bf21add9522d8adb751a7f4f8fd2be81a02006447c44b1eb285be719cba4354efa07894d856a89ba0a131d3daa701b693715f4c005ffa5de7ccd7e4a6b23f36cd17feb7d4c39797a4d776c7bef97239f5ea23dc15f1480b58a0155b08e34739f42a9f89c1f588f55784108fbba36381d78c80656f6f074cef273daa151ae8cd5c69696f6b2e11b702cb6895d8f8182c84149b8901742ec54fec47c3d05cfc035ea11fef986142412b6a364f876197efca94fa9fd666181eecb6816e42b16880e748699554cfbfc7c74cd514e0b685e2a30e08652a36151f518d06c4fd78ede720015c7e9883b7cac510ca5a27a828046f8ce3cd050d1aecf86f5fbcf5125f13c423536bec796423434f4ec27263c165ed326231c460b263af5ee7901bb7c17a82060a5adff759c347ed09b9d13e9ac34d33791366d100ed06b11e5b86c9df33d24cf048ffd170bb56e2aff7a042662033f033c0b289830d51d7d39fd934633afe6cbf105b1e8ccdb2d60ed63998ee2b0671deb998b479cfd9d2c5871f9145cd1ba0ecdec84681647a7ac9b974c281267b8e9a2ea23d3cda206f6b7b6e7ff3cb2befde0c0ae73ea5f9304bfda019e2ed997f5690605ee4cdda811f90b90e3480fb5ef8be408e201453b39dc557d8a753ff479eb8eb1aa4b607f4402ddd7505d15b75c4d6a292480cea5d63f1e6dd806906a7e28e9fc4ff78db05c71dcc4e2ee2b5f349210553315b810936c740c5c5a757e1775b724bd37646688280e0052a8fc5d313ff1ba9474ceb35207df82669779cc421576a2729d7faae08ea82df27f3fd36940a445bd16deb12f92124979b1f5c6c9bd632296582953b64f6065e7ccfe9afc22ddd3d80685ba857df507491eb9e57947b967c7e7290cb2923504d7c6a8193620bf60e64e005a4c758af2c13e8f8a7c1b9cb9b6516813e0721df2c89aca88938c068037094b1c52d55f251ea9fbe0102727b98a65be4153ef2f679c18671dfb32b89804fc9f964aef0e5dff479925ea1057b7fa0c583d6629b7d0d712bd5ae9b784260399843f4814a6bb358229e5c2ee12ac84e560f310e55df1e114b84f23ccb3c5ab1a6616c25e9999260acf196841e43630fb51e6e5b0379dfae4b07f83bd952c61920e49fc202b735cdf0a3a92a769330ec4fc5277f9371436e8fb383765ae29eae79be2fb847f1cd60e304f6cb9d2f530e4d08917e7a8f74009e15696669372b544cac6b7bc99490c2f4b3c5a1b652415af98e6e83deff3e9571029d29e5729ea1268c594bcd8526e37ba55e245981342c3e56f3d2060310bbe46b50711f901a11a9bcff15e5f8fbe789f8aa2d68cba4efb69f4bae6717584086c886b43a03e18edcc9132d4ba7a1e00b5ef0c8ffca6ab5273e5a3773709b90c81b7492b9d2a511f11d6c023546dfb5567e1e096cdaa0a6bc8af6f9da2c31a79eb438861d79dc302d9c6d9cf89f079c83e431241df6ea31cbf316506b21231a912099a4dce9ec8e853c32b68e3e675937ba5e3a08cf35097499baac816903704cbbef1f706836286ec864d611598c256eebd9618be220f3671b69d9d3aa14911307356249570ee7c8b752e3279c4b92db8ec8c993d7a29371a892e5f953b290b3bee2d5c2479be80ada3cf619a0f0590bd1a2d3db4c4d0df61ead78877b7487d5c2b87d08b89a59124c8a5af999dde6508fcf3dec59eeea9504922eea667cbdb6bd714f7233408c74f88e9654a500c3057a9fa21ca4a627f6ccda326cab28a89d899d813c5ce2040f2654af2c86dab371857cec3ab8a9fb7625356d74cceb1eff7c2c85c8e95f493d696486d07d7216facd51367518de2d5bd86536959b1f0fb6d6f26356d0043b0ddf4f6fccd5b9abcf4ebc12ec8b66caf848eb2a9c19a86cef84565f313aeb6fb5e46a27d5e401e08467fa3067aed6b3f8783432b5d048dae9a462a5802f2afa8a60ccec4b03e41bb4ce8042bc609653561b782aec8528bb1552854077750444c2965246167a915d5f40e7ea529e7d0330754596bb32645622a67cb084e6badb7b69c881ae1d696f8f15f85eb67338169f96dd32a0300ee5b7bcc3a1329d614c1a9b48d8cce7491c9bc2b472a261eabc9bfafd19f07bcd7be5c7f7afe3c32755d2e316dc31c75b7c85e7d935c54b801a7231197d9ee0415c060335f3e0457b4229b420f928c9ace82023cfc43c06e0719bc2c3daf762d51c3e4b65f55b5cce4d6240e72c1ef2fff327ee652c747e5dbcc61aa6076a0021465c279ddc627aa73de112e838b93b107461ece95912e3944a8d87ca7b85a5f02eb958f3f3074c3237d0fc1f451764c41935c3597d99263975bd4b3f95df2bf8e8b2ec62e0bcc9019c592faf214acb4b260efd8c3867d01abcc04b340ed41345fd3c1453b0cffcc93e7026627b23d145b6ce17d2665a435df7921a32c1deaf5967b17d755cbdce79774faf8f9f0f62c571cbc1776ce1b3412c4a79573094026d85b149cf5b1aaf044c2959a7b3b485b24d7bd38781549194cb13ae3641093fcedb7e25665f4dfa8654dfb8ac4dbc8749f40329a9f5202505f0504d8697438d7de1fa3f39b1a026e86c7d9eb28e46379d7f683202e2c74f9e704f15096c7d6ab27fbc8946e390257b3ec3bc1bd06170a2fb4ae2534b1d6b3faf038d4176475675ce1aa1d6bef6f53d4b529160c93cdc887d1c1d4bcb10bc68f038a1560090a784a19c6a1ec94db4a45de9d997128bd5ae7fec6ee65fe9ec70dec9442c7dc95f55c6a2d5494542122913f885197df7912238f3559fe0d9011ec38452bcf31f62148c2b4b3f2b3abeb91b834abbaab21f3dafc4ac7b79162b35b41ce81b96488d4b3ebedb044a9236add0178471326b7a54e7935086d6f9053b61a361e6402e7b52b31cb11ff95d0bcbb75364ad15fe6ab0ec698c09037af879dda4c9a50944d9470705f53287f4a7393901ab953c9450b376587c1956c317abd01dfe095cb74341357df6f8c7d13fbe68a2bb468294bd00f51dfd6b107383eed41bb6173245c999489f04d848bebcd6719ab22bb763cf87f2480c72b09be465909a9ecc0b148e045b44344b2513b511c4d56573fa541724b455216be9e6401048554f70332fa042770dbb631fa01bb03d8534520b99c8a0f71aae487d3257b72921611732169daef51cb0e1cb2d1533bb3dabe75e55213e4213022031a808016d27efd5f0b884b9923a6b4208eeb78e08610f6d735a48a8c83b31592d4884118cdb160f82d9dbb3333e136d81b9c833804bfd752a000f448f404f14a9c892ad6519e54390ff1bb0cf6365612bec097ead6b7b217c5f46daa39b4a68322f650a3903fcaf16d9a16e673eccfbefd1c10fa789ad8c0a767725c2672425082b33ae130b9bb37ae26eb4bdee549e329ffba9ab5510a0988733872be156df6e56e3c600098a80fac0158bb283be0db9dc70f901126df3131d7ce7b62e0dde3b737bb3717844012ff9f03abb397bca415a1c955543936110e47cc16b321c9bcc3f5e272272aeac1d04de9974b2590cf0ae2f74c7e28d35dc320c7b8afe96e72719fdbf46b64912b43f2fa04ec97716583fc1e76223437b6a2455ae007a4ad719a6400b1a79284aec89f74d3a37d57ec37fd24f8bbb72dfe17d0869b5e3f7d7f3edf4f058cf652c4fbf72a7c8faab1c54e4ab44a86c0f515dc339630eb86826aa427a6526f3cff322ba7752f7545c638da3c17c7516deeab47e3cfb588ab50f3ce6b810088b456f62c9fe690ad1699f644899079725b9844b3fbb24baa5293d993c6841ea9101d6ac1723e2051a3eb08b79ad416fa6921aced33c03a1e8845e68b02463369b80d4836f88cafcd340691ee833403638fe4526e4ab958901b87acd681474209ff98882a516df863758e3c87ad7e36e5018bdd4c5f44d0df3a51b989848eaeb3a34eb7768440b5af2b66f2a4b9ff499cf099b820dc800bf69107fbeeb90b59ff72649b6c6ee9631dc1ccf0ad3e4a4dc474648ecaabc939ee908ca6497beb08c3ce60b4f5b5a343c7fc4944354fa435a04cc9405ee79be6c9b698355cc9e6809b525ead033512814b466454da89ea90f129ca514d4d5f270033a70d80e40e3c3fc2c9809a5a76d1fdbd0f13b0cc9b03bc1915f2a66a6a6752db57e658d75982b059a6bad787d9f1a6c03056abe33b8fc844749c3f575c8cce39dbf6968dc4f644767339b27840b28b4496c0314f87fd50c0a423d158a7231cfb5e6ddea96522a9c0dac4bdbe5e5ab7fef32d19d346a8c5425dd2a146bf4eca9fa322914fb1050e5825d97f1b4bd133e865d062aea17453b69dec9ec5d85c923526ec5e8a07556fa7e1c09194af6b08bcc90a296c12b30c41e9aec1879e0840822e9a4a128883e38cf85e4f6f33447e698457546f18e91bec34c80201701637a7b4e00bbcd7a0b60eaac3baaf3c5666931223bb3e663e962deb2ac729189a1861598a824f7ac1371aeddb736f1daf7a9059869ef9a4570f2afe6851a987d84ab3b4877c588d939714cb48062f9993f4d580d60f809330b825026aacc23f31dbcdd92372578dee0ac4b5eb0ac74e2a9cbc7940fbf2f3c109c5e3edd1811529f0b7051ae05c01f1306b3d0897b9cbc4dc53ba9f3bf2ef14e118944c5427558bc0ca93faecd718a50c0afe7fb318fae4cd0dd0c214b5d6668f3256fd3071c9e8b8ab59e26bf53b0c23de048f57b53c6eddc73c54fafc3130b6a0266637cf0ec9dca5de024637582c6c54f30111df6ce25a2b0d99f28ea3c53b1db0cd8473434b435713d723a92ee3f85370dd902b3d2fda44d16204b857ce36ce13a8daa73fb3ed0fe1785ae50c2b10a34b9c65ba3ed18f90025e4977a8cf00f2877d8367f191dc23446209ede3535f84547d7269166dfd9ddc2bbe7ab0098ae37dfd44434c4bd61f63e842f1831ef4dbc8c3034f0ad6b3e83bc61e592f1fc87d6a20236d7ad6fa682ac5494ceaf92de7cad99dc891c17982f8bae38c073bbfdadb2cea61982504d08e6113ef5300fb527b566f30078e422cb087cb88a8c98ebad81a425b527211b8c1485bf35a3f10cd1653d83951376f8dade86924eaea76ffe070b52834d5a54688073c31e02c19f9e39fae17dc2551799b0bad037027d53b74577752ebfd7551e0005586562e89574fc9d22569e43c3fea58358b6228742deed690a057504ad0d3da534a140fa0557c43e1c541a2e4a1b99418a09bfc22e2db0a1bfdd95f85970a1edc1a6b6b77cba3af5fcbd9044e6f9a78b5ce691db15da6dbd4a62fc9796adccf47d9eca579e675e645f035bcba03507e6ab01d6128597a6deec7d76b27cfd867ec83f15e0478014994c7b2699027617c9cef5e191de0837736cb22b99f6bc693874ac60020955d4539b9ded9d29a9fae7b07a4e47f3c9d837304502b10a9f8ed19781ae2847224f54dc628883a539331ae238dbae73896453f94bceffd8a7e0d1fbdd3c17cfaf5fe135d7f7b534ffbeb56f3a759fdaee67c850b31ce9278800eb761c0dd2aa73328523ef54eac7f9d76c2a3c4fd2e131d94eb878f23cb0fb484213925a66d897e2206f00e9b47dc825bab6742e54006db55b06d96ffb54ff9abd78c0c46da237b7ba8f06dd5c3e8d309fa46da147d21bbf364d11fc1294027c16bb1386c11f670878e8da5b3bd3639fe226abc4dffa55b828338ac57a6d68a4fca877a7ea9809fc3b5556c2051251813c47921429435e16577775fd68f6ff907d0e3ac1766ee7e0b085de881dd3e9fddda6c72d46ae198d9e6c54d0b7bf643ca6d5bf2bb49eac14957d1a08217fd3ca3707aeffc5311b23756d094b85f19afac979fc386ab2bd4091171d84ea3180dc6e48c57566c8098db52b0ae4d412c7fc0aa765b2ca173bd5b6822d3675f8a50d9d261ef30cc128196b6dc5408921eee532f4e4244b405c5d9f81baf2c2d9aef0dd16ad862c3b2db6b535ca1a794f3b5ecbc65d9f196786b919d961adfb897ecd62982b420360d65f3759c968df6383781baa9472081fa55278ff70905b36b37375cfce3fb69bc0440f9200833ca768fc8e8fce0c5d8445318666c13a9c3dd29a12b507ff6cdcb2655cc6e48ed40c6946f1085bc93b5e44f288f91df8afb49c643badbf99a81458cfae4ff16b0ec4913204761251196c5dc4e766b6babca48691e1d3caa79d363855935c331657251f9e32222156b3d94bc89e8502dad6a168ac7285b10e7fcb7951b76fafb998dd96026fdae876c83b1a84772f838c9fb41a018e320bbe1643f6d2e7d6bc49f398268ebde8d9db06260695d8599bbd3bcd402507bb142d0c62cb0817c71af9e036939e422a19fe4274446b4633d684095ec4d2600751837eb3f09ea910ed164723fe169f5acbf8f46cef5de75c0c1e894c9cfbf60745f05629eb5178435875e04da84c0d03da951791147c5aaba565b9794e60bd795ee3549b0737378be64e6513e9163aef34ba713cb3c3944192f907282aa5c85bfa6297cf2f2f96f45fbf92062836671e494b5c249a03aa17432b2e453e73f53c29970e728ae0a43841041af1888016fbdbffd75a0c2ced87bd44d6cd71d6e972026b29782de944585ac9f93a4e76523601a12f4740cc609badcc38e1ac0629d34e9a6644e65a4ffd7ab0999e0be0685a3d83917f64953e7eeac3ad1131b5d0b0bae0e61c0f0888054ff63a307f159b2bf2f1717cf890d4f8dca75075e16a15ce58ac35ed8e4f0468c4fae0259bd8920e60bdae440eb5c1aef469cb3a8eb5907515c8fe119d841adb46164aa5a467e5cc549fea1999b1b2a933df5a7dcef1ce708d34d1685c8365c6111655e593eeca9cf720c36efa664c00f9cd14cadbf72cf64f1d3285ad922bf3732f9b36f32120f69758123af26a14bdfdc6f3661e7c539f8e7f4c2409ae52c462d8cdab94014348a0fdd2fade8be33da3e54a91a7a57c9349e5819d050f9baa91ca0eadef6aae9e59006135f600354f5357b4c94c4863b313b6385f8ca0e1f777ce8b2dcf63cc8e1fa5c63a48d2f104dca496e6ce37229194172423f3824749bd8cc22daa415f81493ef1570009f1a7a0fa3dea93a57ff2dff0df41562d1321f4dfea7f53f176f5b9bb355c427b39680ccaa3e0cc2902079edb1d1631c15cbe3cc8062b31a55409a4479b7b01cbe31fbd553ca48f3d975fc2a1325cc15cea0a5b7039096f7376f6dfdbe9fc4de7c4cc7a49e1eff259b999ddb5ce636de0ca747c52aadaf62d68adb471069f935c26e7427e7d9a6c7f6c7fa6ad7a43a0a459bb69cc3be12fd5c37372766502a14f958b9babc427a5d3c0d6ac501af3f57b775bad96f8a9e735b25fd1c3082558fe2f31b78229d534977e620534d653669176cf55e9a6ab3a4b89c1b48cdfa547a81dcdd4dda637335daa27588893e77f3751eea5562bba40f4d355af9b7a22bccc7cfe66061ac7020f498fbc48e0490d8194cfe20ec2e85ee1da681401557e8db21ee6bfc504a05e45470367220a54a33b7121f2011049be4cbb1b2abc9776f1d3c08d7f6e717fc13cfcabb9d774eaaaaf5f0a16b669f11146c9ebcd6422b6786afe55ad086a9a5b8bdbbcfbbe04545f7bcf10b081594810a3afb469a992c7362bd2fe9ff8c19d621fec6f23201ed56d1892c2e4ca45ad65faa7bc7f5a3a2073790e662e216608fc2fd7f726ac2372563fd12988dda29811339cb4fa3e22b2a74b9879a743dda5bd1905398eb5a8943512b70bfd9f39b6cb156a4eee685bb3756b5a43034ec65cfad3e7f2c856f478fca1626b7e2a6d92ef4cf0c9bac206ad2d331441a35cf0dbe54429e51b660e5564eca9211d2b8f03df3debe1757ca0f37bd4acf6ef97c86dff6d42708500bd012e858aed5f54804445705b0bf6438ca724ae345fe82359ea0f15b9de3e1b0ee593d0f74497845ab7f10c87b9d5349e3e76523566e75f0c22479a3f0e324e6455feecceba710c8cb52703f542a7c79cbee42c0c2b6284ad84547ebcf40c046a11d4ae0e1368a40764c9c6dc81c9a869b57f52e7136a5bce467f14a86ff90d30b6a2f32bf3e8ad5faf9bda0816bb9dbf9f1a81605dd21766aa0911f3f0d82018205b0b2e1e746ad2dc135aa1141c4e7bf77b19083a3737670053f06f9c6b93f0a607c8c6c270d5175c0367cd66479004dd6f0fbed374ade63f6c54d0c76aac95a3efe74ab0100899eaa2f73c97286b89591a79665cd5bab20457f882c5d82cd50aec67a333e3b44bd59ba463c658ac6a2ff877e77391df6e8b0f940cc28ec64ee82bc3b5501b81855b6102a5b2cf10bbbe3a3adbf11826207791e6d3d4fe8456ec3408c86ae3631692ba2c177c0e1c154c68c75000a704e1f2f5ee265b25667392bae1988bd75cc945ddaeb8244c7847690eb2da5051be73de200c7e7914b0b4b794d3a1aadf39d800793ded318fdb3693a7f874896c4e4c8edee3406c860b688fe3f617600b99b34e10906513ff9dc0c9de000ee5ba760a7ace62c4559605e0fc87edec57539b670d3fa9b59480e8eb7cbec38385d58689d13dbffb21c468a10c0a387728649bd012fe44a6d86ef4ada137fefe0a0c1eaf4ec22635f04e4b596190f80b84d443273f8f3d1463ec6b5a9f97ce953b52e921a3ce82d4b7a21b950b67000a413ba8af73da6ddaf217c5db32de5fbf1796e3ab04bd9e3d9a1bdb2d64f1aec636a1475a14e1c8d659e92072e386bb2e97c32c5caabab50300946aea4229ab4cc265568e11faa7f1c03267e4b5ea95dc766409c034f0c94c5d49be79dcea0b41c95e1cfd9fba466a8eb7d0aeae76c2c3568bee821f5dc6cdba225c0a0a5b3dee2c31b1df7411350cba6607e555b803bfe1d11df103de3167da52b3b4f782129ba1ad97e58d6bb770db82d46b7756a4e74dcdbfcd0ccb8b75b2ddcdfa1f664f810abd01d03ab7a40483ce21a9586ccd04a7e305189218dbfd2ef575da7ff97baeaaae3b98df5e2cd11616b6c0acbdb1db39783831edd17f3529e14b696373871626f3549086f1376bf78d7549fc3b966d7b0237a95254da689e53e05eeb4cbf6c1358f84e1f8228e5565c4b57ea870e4fc97ab4ff1e1b71eafa5c95d023d2d2ff5a96a5bc8ef5e2d914e8a696e3526a5c33cfde83e6f18d0b75f5135efd679a5d204e41a9d8a3ff4a127b5f4ab087d21a258dfb036ada17205dfa74df13bcc901f3bf5f4749079ff5feb34276536b7614fdb6d824ebb059cfd0d0edad496a363857a4958e5e1fafd688bbb7c859f35f9f7c4231f8c547bfc07a4d53ffa8f020b9ded01b1a4c631b3a8014c1230c6ee2999d72cf8c58adb6a75f55409400569f484ff2d9c95f469001ae3c8b869a9d4351066e6ec40a2336b55b4e9dece076f3ff8cf39dc5f83f42df6ed72c9e2f33cfbab8ed2de4200c29a622017aa60a109b235dcc573afef5cf48155c6e346d2a4df15e42ce127e0298a8a34d65846303dfb111347d2152460d6700ac9fb60a82c87da2fcb59d08597e97cf132a5093f1bd77498deead5b6e00a165e263b9c5bd7e20b1adf26055cdcdd5f8b0f15d1a30a71078d724c02880d22e8beb1b6b3c0f9d1eeee5ab36b60003ab2ee26c1478aee4b84f909da9ed5b02da71e0abe552d48c1f4b0775d3b5d7734f89f4a6a58f595eab1071d81843e5f95f9c6452f0a423b5ce1ea595b671ab514fb15e7ee7028c3677a849a6ec4850a24680d35318ff64459dcbffa8b4d4e4a1544c4b4927edf6d0e18f554215cd3c4606021fbd810ba2117b05078422c18fb1f5aad375aa0725da17c555c72329d24dfc096329775c8a71265471f5ee0369ae6b5543396545ba4e6d41a58eb5c5680d148b8269e276a501d0234209ea7093ca601809e1198bbca00699845821412a2a9fcc0698e4bc5ba41d0d1303ac9c9009dfae91c0722ca63572134994c8e13fd8f4868dc8bedafe24e30cf4e6d87244e44158849f8b9b592cd7436584758bd55162a289691227f42b0ab80bb5b7d81e931ecd0a9c2276ca9da4c98c4d7905df32dc778615f8a722f7c3240942724ec75b04c37531be2d2286418a32cf29afdf6777fd7346fc204cb84d35e1dbba9f1c708d0196c890ed897a8bc1a06e67cc07f877a063c5bb8d57f69f0bdfc58ebf3968ba423308b42ca0d2abc046e18db75418e449db791d31c387a166943172844aec297079fd908cc3bf2b41ef2f9c2c439973d56fe07b8fc9317ca7a638b23ebcc8687bbee915aaa82770a74696c8f9a14dee642c0d63776fb38070875e88c3ba2267c5973ff57f629f205a967f28a658a9a2b3b82104a2bf568fb9d7baf64639b719c39b93209ec82d4a857a43cc8228114c7c9bb1774b6accff9cb58a08e507d697fd16d093b1a6e235a22101ffc3febca22255f224bcab19d2adc0cde14d48467a615dbc68a9922b5240de9b6612ca45b954b7fe91159da99f84fd51ac4b8b9057fc23142aef241ca0a7224b2fcb66f131d89038e2660e50e1d63a873666149d52607ee6659eba00e52b5be4273594b1a8b23ab1fa2aa962f9956928d13f1f37951bf408404a8709e48ddfe2ee0952a4a577af8c3719eaaf0b346f51429b745630d4a547c893b509dacba31418103c11d7ad078ba7e3a6ebab2de483db908e6dc9b52800621ecfddf9526d9655169b6a0407c6dfe6a2ceab25e7d917bc2f272c3711c5c05fbd1e727981485334aa07304f229f65ad9c040e8d0938045fd0c254c9beda46e82b13f96b6e2866a7f699e9e30eb3f56df22c7fff6b731fc37d89b4d1abf811822302c4f52777ad821b7b2fbf5d04cca34966b83518933eff99627a048149b642ce87f3005b549905ad9e4c68e899770c827e4b03d900d4d499e09092149dadd21e55cdbf77e5a044ee4a573477459b149925a749d02e3d10f3483e037c905e7a1398a732f5d907e505842a5a2cac3f0945c2e587546358f02034d415230cdc82f2ae706d50e3ae077386a7697ee972826ea9b510ee6004a2b7496ba4b1d0aefdf936e11c4e7e94b7e6fca273b661da7b2011e363c3333bbdd605ea46fa8056820efe9f2d7ff001b251e1badb1b65b121667943e9b5aa850bb09f4abcf91f5f55f2fc1fbfd4129f4ff805bb51b31ef34b37dc755c01ba091e08acdf984d3b0e01d5253c3d6f9e477a359007c574a0edc99458e34822f3000528aadf583c47db9e8baef3b584953704397a4951a40171065e69cc04006ebe43fcbac13fb344d86738c4b214c37a645e3ed4ff5b4af7145a5501fe0b4ec42c0c7d7a539c77cc2a7aafccd909a6bc8bf2800cb42fc9d84dadb84fda481db84242192dca31981b812b2fce5b3c578fc30209029504e27dd52041832fe8b129cb3656a06f560324e6c891e6986c9f24d00b749c10a52f00f89345b7d3060916c299e48bce7db20a2d21d4c380fe1fe3e1a2e5d2d928a60c333167fe53118ddacc5a9b8765c00c985553489ed577fe3faf24736747f5db548e284100d0167727fd1b827ffd31376d467377a2c706e871d6622a3e1e2048d397b66f4ca6879512d09c30876b58fc58008157294b5e61cdaa5c2b7f8adc4295bb4b48ce62d4063f4c4aeeab1b978b2d5b05eb5c8fa5ce09f3cf51bf46ebb211446984d27c1441b4225a2219529e6b086dcf8d70d282009712e09f8dda1c587396e860bae929df1d356fd45933eb9ce2e83a843af25ba743e152ed9ffd9e921c43973b96d530aff53c009f4b78919294200b5d9709eb6840db5a632b1f9ac5f56753c5c07aafad4177c5b6590665cc9b97f6aa4d994a761a6fc1d8186fb14af9d35e7bb149e01f22aa7b447713302736971f79b3df052731ca337542bae1396738ffe2073569c5e1b0fcf184a3cf9219d659e54e95417db18fdf304a1f643a5150819e048342de30346349f2ab0e5f04a681b6a79ba0ccf65bc5a09fa7fa9fad75a7c953123232ad5392c81166f4c03970056ce11341f962527a7e5274037b67f7ad4f2a8e56a652f5ae56e6b9bf1d9fb435348821c67b99146def4be8c232a9ff0e250d9c3ebd7c5dccdb04ef3811e62eb5f0a19d37dcf3dc3afa981a93e6e81f5bcfe9d999b5ff73f8552431a1117550bd0c9cb3a766dd9301632dd6e222ae6c0600b0a347bdc477a80bd25222672f330486413c6f61d4fb6f7562a61a5bdc8fdfec6a76c17003b189ea2641010f92864ae27c5dcf81e485aaa87cc1fd77405d1d8a6ac48cf1826cd786ce516b75d867c2d6f74b3f22d7616b90d9673de64e1851ddafba5ab2245c65bbaf4e894fd19e60073f9d156c0bd36b2300dfc3d5b4ec3e184e0dd74ed43e0c71a08ba3cb39768b501576b97aed2b007c5c8f340244db15863e5dff4d53064409d423ef4b60a276ac83116dac2de819b92307de1f92dbc49225981d6d28ecc92022554e59099bac6bd76c7a2f501bffbc2ac501c103849788119248a7c2fd1aa1b42b2ad7e62c5c473a0424146835059617b1b6a6c005f416175ce6dcba3b9f2f2961b6ed0ee68807d5de56de18b045b025c84266514681c1fb1d2cc382e2409799cc30e8bd94d5dbf584b1369bb00c0ad36fec22a50bbaf30178f400b3b111fb6271251be27ea8916594de588f816b34002478a1164007900b9882848da7866dcf881bda012cc74c763d4d9f9e8cf5336ffd6e6acc94f2d9cf8d37a9155d413490666e5e06155739812ac7d9e7c360783ea5508e1b4cb6b9d4f2e60eb230806072450bad6ea5abf03512013f56773bbfc0e41572c78a3dc11c0b2893775e34dfb7aab4b529cded511098a05c9b67a1245054cd5e28c755d199509705ad4ef7bc1ed4a655bac34535f427269df7e8cc86ecca1c6cda5a4497625671b210d2749c0afb825b494d7522fb7450819b9b980dc7c2a74b8829aadf6daa3ac18b0fe293d3cad81288b9c8054a8e01865b8305121082e38a20e35951fe75f8bcbfa67e6b8098809b92f20ef649f4e43e49fce54f9c473075c48c28eddb703fd767608c48aa979093880eabe8d3cac4a6e6a268b9569788c812392ebfaa98e6f4502892e79d36811228ed0e51a8aade3c63fefd27070a30ece1538c8708a3d4d7e14c8934985db727d49fb3639ab3be9f1de3307c953150798b391ea7186ffffb9cf905d40d1a2a2c3fab6326a65df99dca88c461679189b2d47521bc7e5dbce20d5ae740921cfdae0a5439664603820ca133aa64d3d3fc96569d0846c8658a6656a3b4fb54c5a33f7fc61d960a2d9ce16c1f40ab2ca6a1c21c733b386e4983d187ff136aa416120d5dd71865cadc0d69555083c73c59cf8d3840b4e8461cd1adceabad006087e819f5326788caa144f8883fc5bbaaed03d833583787179203d5e847e6a586c480a1e8ef58b4f5a25019feb5d3a4df354352c3bff6eeaceb122a6a48745f067df74b6eb0d05290014c35dcd139ad0dd9648e24e2786115309c9e32520daf863d53b95cadfd8e545bf59878a046b4e5251e4b07831a236375665dc6e17f0d9bb1d4fb0216f2110982da2a7254ef16efc69e7fa3a154a7177526fa0c3dc91af92aefe60f0dc3830377f7cbb45c421cb741e69dd139d67f34102a274d505de3925cb2ddcf8f284d5fef8c42a26658596a7c25d51d1da5fd484114880229b837a37d250c9edadef0f7de9c702417be33cd57deb6133db02ce1f03df6d35ac32ebb070be967fb3b710b63cad6973f9beb125d8146f9421bcda2c6240ca1534b2eff128ccc77fe939e5472dd72475c2fb508dc9ab3bde1f80c3bd6f2f997b846e3d9acac010743a50c5ee5efb34c0999667f9c25a86a9ca5669900459019ad5b503dd4609d664156b4249b9bfb35c080378822a390a88364640f579f7108172c390174ea64f7e46522a07918f1c2e837f3554ba99331d360904ad748fb7a917789d50d3b56b13951c49e4843c0cee628595c2150295fee35cff8a68021ab509c055a573ad0bf22f016f85e28977b8c6249a2672152fce2d06a30f3c5434d482776ec17c8565f977776fa92de5fc5881ca71a988d09054efb145f769c8a6e9042fa7218bf62e0f512d8fdb29b2979ec36106da60fc46e3d0bc12496867ecf611601354d7b23bdf6d2de489bcca120dd28fcdc96d3a76050d93a6d6e1c11f0be47d3fffd6d5db1499b21eb6486acc4d8f8ebcf5c50f573c5a2fe67fb515c63c69fa23197226aa4edce227069d941dda18fd3901c408028e614d7f7b951ed0662ed2bf7f77b0976039c363544bdb0e3d8f619b517c6e022c922621f35722d3938c83b616a373409c85d431e3bd5642f3456423d96cbd09517a0b8c59c0e4245b0caca12e028118a10b56481e5088c1aeeb6d5564a537de9c76efd0e4b4f435d125c903013ba34b26a803d8c768ed0e9bec1a9fcfcfc686d64431ad722787f9c27be40c411a5ae30b4bf4f1db5c3bc4d25b0d541ed0f415c3f30763084c02a6dd2d58763bee4f3b658c8af3f057d2ff3b78c5b848026ca929953f3af2816e919448904b2e51d7dc0179268808234767b16d8f999b2eaa6d44abff365d742dbec67232e53ff454785fe15e9c4a590a644152f985727d6188407d6c69493c3e696f9e04ad85a3f8589a063a1d52377faaa8f20e2772374cb452b64c0fc3426797189bf567d456f6c98174cb73d162398752dfa26a0efad8c06532694a9976fc76a66a7c1dbc5e26ac4d28892ad4abc9f3aa7e455c0701b0662bcc66f6b96121e4ff2e1e0f8c2046ed8d936b461f7a7cd595429a9e741b43def2cd13d8c511c0f0dc3d636602ead82b4308238d3a769cd4afb2b4774baff3dacd53e067988e166275cc4eca0c8c0a040255f8974d138f4f352574924d72e7be89c1cb49c86eeb8ca949f00ca969d76da84fb97e94c92fe8a8ee828d38094a2cb8d4a77d715b5286f669678574e76d9bed0595c25d40ca99209a99d1b59923586891b78b4fd1e7a4ce853caf30fc3b15459b225ec48c728c40180a793d59224930c1e1fdde5a5cb51b560e4fc0a6507bcba9f3eba5ad2ff30d54da6fb6f7165634e1be4e84730acb1f276771cb3939fb685acc8efee03f53616ad5d33154736a20651f68d76b1411d7381c3d8ac61a9243b994de5c916a4d13b8f4a68f8f0ee698ff828d347294dd47d55335d62cbec8f7e8504024cda623c9429d19d72a3e59db75bc3b47da5c6b738bd20993c269623f297a059225209a17fdb64c5b4fec1351732935a4d471fcfbc0a839d8fb154fab72d8448833dc2071ab30add6733c2c5349f08e0e14cbf1056bf0aa43bb376d0085aa449e3b8a0b40cb9e1572b79077542f5779ade2fd6f369921ec5b82056e4b8e76e9664d18ccd3b09a3ece37c7ae47896cc02d19602a67ed24e1529efe82ab976253196cf231d6fcb8cbbdb55104fb44c1cfc4e91b23a3b5628289a3764c6c8c8c6456eca413d869821bf2db7094e2cf5b17540eacfac5ad465dcfd2d5492b21fbcb0f4ddd1809938dfc057127f2ee0c027d6b4eb9a513bde639a43050f4ce7573b54ac2e7cc04f5abb1f8a6d4df7a85ab38bfaef733ba384c7fa01387edc43eaf694ec1a739fa7fd4272cb0251f8fb9a002a72a7ff1dc5c83972ea64d9bbdc976e1d316721036e5aff4b8af070b0e26da6d5b36e1cd9f14bf6838012dcb2f522cb7e9f76e9d0a2882eea6c6cc1533f53149f1392cdb601245d2e64d6cb6b913250f825239b8fb8446a347ded2e6ac2adb7d4cb6cba13a9096be51ce942f69c30d94b32fee7c572d6eeb9459bc1cfdfd87a6a37147abc1629d710a4c9beeef59d15ab401f9b3d2b5666b39e949af0506495be3454d61d63e2dc3946f47446bd5954677798cb764fc6ce074b533d45fedfb23544c2c49f6b6111f7b5371462f38b56fede48606cf27b07f83c4b930a9feb068825d5c1f763a8fa98d465825841a2d912e5855a84d3475d42d83ddbe6f780bcfbbdd2ab5e40e69812df52eb782f311ffce7cfa0f1634862090b3f4d1b478e805e0832a9ee5fb0ea6c59ec350dd55f3d748bbc878d5fc7e2e882fefa6bb621997f00007719f9461327c92d780532a9790eb8396ae7005033b01c21f18ce4a20116e31ed681c0d9f7a70a88c284ecf69518903c2dad744a4ccad982165955359c18468ff4a2f168780112d6264bc0d000cd073eda84935038c9ce99db162c445e18ccc9fe5f0032dd798b013d8efaefe7da85d759713df113b7269f63e8000dc723f8224bbc77ecf6d62ec6ae1d5e9d499b4c3793cf417697b756a5fef1ff6cd70ab5e75d2035aa1d247987d970c35e2b2dd44b44815e467bb32bd78b2597f918b5a4fc10c2a4edd86a863628240cfb7b2e00bd0ec78dc8e750c9e4da551be6cb087e1ff86a1d49b776696500f20310a1b6cc7d7e82919d971403998c158b746636f09a82dd9651e758b0c968ee574417e93f97980e84b1489834e4b62a70fba17af1014a948cbcf51cbe30dfda74605b7f770096e36680ef3ec1f19fec3f4f2676a74f681abca1df81d831657fcd7b39d43542bbe375189f2c1038b9d42773add4701236b239409b19d1bc15fcf247caae23729230733f31c8816f4b86c88fba0069b54b6d73d338a5d9462412cf04a20f66cb883a4fcd4f2e40f6608e2f0f5c62de56a4e68039bb84b910e9081680cd33a68d65963d3e1526e4eb3ba347438f927fb571db47e8d928cac734d47ae70a7d78f82798548bd55252ada8b6348ed9c65d4258dedf76fea85ec2edf0d6d1386d3cb9fa2ec625fd232a55cc2dc907e18a07e682ecf20d4dcacda6b06eeaf0c6a0626461394150c1825e19bbf73a1335f1ec83259e06798870b6145d4b1a2dcb1cc62c3aa11209810b501cba22b1bca506cd749db74ffca0a63a72259229514840a952d94536c05b3cf9f6b944152a4e4f57b92a6d22da89b684b709d5762acdaa945b5b56cdfe696478752815683789e10fc63e2dc79120c9ef09abd322f5b16528fb873e9189e20d2d3652c46294636f453d726dc83d613ef1b923fb9951b7c2058302dfb98520830fa825b2f038e151f8f78dcd49c3a5874f4740ed095af8346601d9d049c0399ad2cbe43c9e090e09ba2e08f6b99017631eb572bd4798ea18a5537a46741e4f5ac6892a869e927e2c4c40a4afc6cfbc18ce089914cafa11887093ba5c0be90cebdfa0cc88e95f2e5953cc514547791934ae7696e33752108ef05d39e21c41cae0af1c4c3e4a4f33405b822496b55efe662ed501a1706645e19312387d19805cf37f8cb728dedaee0fe4f5b4f86059208be8c29d225b1bb0cabcfc7c616e669a4c256e5cc0830407fa3c2bc0175851dfab75b0607147f48fa2946d55ce7b79941d20d3a67251dd55db220acc9c194975f2db3f016ae0486e0444ce54e1915a56722b6e9a3142b9aba2d595fa38542a22ad6dd1fbb0e84e3bc2497ea096095de45efea8f1bb253153324216f35b40a6b9833ba97529f856e4ea5fea3624acdf50c8b732ee139d37a820becc75fbcc3ca84d72b4edf233e5cc223a035cfc227733c9c453b1075d847b41d9c31a184c6834559c2720e93eaf0680897456b857bcda5a26d41da42f74d41c20992e78196bc17098eef1aad1d5ca6bc5bb5308ca493924e47d6500f517a727fc62e8ccf78e063493442000261bca3e5c2108eb70dc6b3409c48e0c109bb4e25a51ec8056d381e128ce6cd5bd2e9da0c3d2a6d273ddcee26a9749aa3c88e89b8b67b75a60d644ab4369819f535d420cb43651b511a5eb1243b1e131a602b9e0f8bed111d4ef60c9a6ce97aa3a10f031b986e684ae8dc2d4fae553e8f6eb2e07ae135674886278436d345d27a33010c5057ae872fd797b09e984fb41816a1e4f6c8a05203387ae0635ddda56aac5cbb51b4da7acb27196a42acfc71303f738982818e9b5ffd379fa00c1f4c46301e5f9b47707a2388ee81c43c1d69c55a3994ad4b330f346af87cf5ee51089b2fbb0ede3b44b68b883c70b8dc27e5bc5ad4e70b284e935cd0256ef2eda4d226bdc2b9a6f60974070a770d98307dffb2dcd8549a038061706fa81559d536e46ade14e0f5e41d50cb13974f069b1c1fd9a095b590ef61125b4f2281e0a28614f8976960100f88290eb558055dc084a255e1c5fe48f8218598f88a0db711adb4f8a3e60e6165e6fbe7c79be6355ee734f5e3eac3da6b011e6a5f038e8c90a9450413f55bc60f8642084ef39e1d171cc0433e16474cb99b91bc10ab83cda1b62f9c1d09419fa45b6c711b96a1092129fb5ccf79b37d6202fa15b04639697d8b866e97b3b4c3f2b395c06dc6d2fff9132a77efc6c809c522f94037b95deb399719ac0a7a193e93c70c9d51b531237baf3155c32ae959454acc48a06a3d17c9f95db59cb2db1e6ace42d11ba966c9e63852ab7bb92b1ccf917f44313ccbf550c7bb274bc83bd42deeaded523b03b6baa7655ea9cef2d018330a9180853967721499532c7a5e75c229e0a64f4b3abac937d1f02569c59d373b4b02e735f61aee6cda164ac809bde5eef1d82c2f895d96668048c8166524631b862e3cdc8db7ab6db54905b347b5ef124e3fa9b58892d21ae34b9d88a0574e81b73de6649bb16a0a8611ff9d026469f84144cd0d9e0894146a8461206df2f361a92cf89645f6ffddf6e5a31d73a042cd6fad5b89d076f12c2f87a580a91380448cb918b18889f29bf293e6790c04a321fb69a6035a937d5cd09098b3197ef378b17c9988dc13b51b8e4cd5b76782987da65d864e7368a5b1a802add5f8242a0b37bab53efd6fafb88efbdd6a87e81330edd3b9662e76b896db02f2f081f411170018f9f9a6b454b66c8b98806346c233a60a5992e6fab6efbb7d9acc2ae21afbbafdc4ce2800a8d009b8e3602a56214fc65b36a2ec5039f840f49255a55eb8738d341578af9022c244f792415dfdd7b0da8c05998c5704fb263a4e326b8b957a5099cfb34d779843611baba2e86a308c857cfad7aa750091bab3bdffe0cfaf1d5f3730713de8574462b5d858767519d9874ddd4aa4527ce50562679329567ea1e35a0e25f9a252846bb205973d822bc8f1b6f4687295c9d4ee3ef768da07321a932019c308d3cca44169ae6b1b2b819cb3a6ec9452982c125cdf8f9e2edef06fe418237addc7c97063d1b876ebde8c97322ec3bebdadf27f07f6b4e61e8eb57fd80f468059af8874e2513267b9eaa7860892f440c5559d6928a1c195218fb355f98e96eec7438460728f45cd586ef42b55e44112f39b4c671cff4ee1f7e043c6ae1b08da5edc8e8bf97eb81ba17a6364e75a2b2dbda85ecc17745d59b98db68462a72bf8525dac8711d25e2795aef22a27ec77830fef2d98633110bfef2fe374ed75f9692c00774aa8eaf8504d822b5380a25fe19460f624ae9c7b2e909a9fd59707aef1ee52c9ffcface4e8258551f2cf19039df56ce8e96c434bbe8eeb2fee533dfb6e523015de3682db3b9dfa5cfc85d17b3ece35dd67b3494ddaa4a055fab0c44dd5a022f779d0c844cc8f2dab8678db751577132e1ed572fb9806f96ff0043be56ec7412af8e10233c425df408e86c8ae886826a15cea1d3e3706bfe38a1087ed23164d55d7e5ad323ce91ae455857a42f8d7ac999225b37d8d5613268910fdf1873d0af0f1f93d77a7f9e8cbf00522a029dcd685aed2c0dc8fe3eedbfe953637a68a9f769809afb8934d45f7864de8e0304c6ea34df9811eafa279fc35998867ec64e209252399f401a0e4eb101fd102ac9b5e1d5bf953e5637c86a08d076f1b0462d832314a6ab9c495d740ef12e166dd61c0fb8933a47990625f6609b5761822b56c9fcb24392acb7e773f1ee97b4dfd47ec817b31e188d5e350c69a2c5efdf7237f292c5eadacf332880c5575137704b8668be4a59bd94928ea669e424e877a8ccdb667f7c5ad61c8243020376505fdd92730c85ebeddd01c2c9d057be4d6f5bf674922de189084b7660d252e22bf337798357a430b07f8ec7056b1e940a65a35897491198a18b2517faf47cb1bddfa2e1af00dbdf86db099aeaef55b078f461a4de2db947d21643bb2b5dc3ad4033bd75f38e4b9aaa19ba3ec7b828016dac36df249954eac77236296d7658b75e9b17f466691fe7daad54cb7ad2a970dce9cdb447d31b2028df6bc92a708c26d6166c5c69a89cb64997428bc31199aa8efd2b6cd94b5e91042e7356ae2fb00b4dd212b80cd6e4b7ae6e78f2bfebc3eb8f09bb3ee5b4ee38fbf2edd9b70e1af037783fa2341b069e979892f57e0e64dd87fa88dbbeda2e3d5a17ba7f9761ec07ae04b0030a836e1ef2a1770e7e9a7f40e8d428b683c5b29e98c136b287439e7f9d2e88066d26ee2247e7b9b99699ed7a7ee892971e0cdf6de2ac3a81ff56a99603133cb090631c62fd76bc5df6945b38943061965cf23f4316963988a121764cca48f09c42d7d289d983463cbb7996f57222fe01694aa9860dc1c7b391415c2b990024e7d302d33125295678a09e77902529fe5e3bb2680eef0948042d3ee4a50921baba5854271c0dd20dc9e53c3fa2ed701b6001a8c86599e18db79f14348d26381a02baae01ff4f0de0ce4cf0b1b1a35372720a211eb129dddc35c27ff289d1d3310db49311155efbc7a016d3ac49451f0fc1d7fe7406cfe2e895b7b57bb15d4c48da5399ca4a16e7bba7874248f16912e4f3501e2c389d2790595856f4eb43b150c32ce6fc50d95cbc248d161783f17ca5eaad10a84929718a0155ab8c2a936c14a5284d64b76459abb8405d1527bcb34f15497fcb650951f5165ac5515c546eea7ce363500280f1a98c22829f698c4297602de06553fa0a65a104814e533d591fd535487da1c023b2c1c1523e37ec783b5e53152b29fd4ef6700c0d6c045dc0a0969b62b4f1a28c1cabff5e936fe096d7a5a9d99b056b5b296cbf7f98202b0b29a75ff192c0b8a0716a7d248aacfee500475d47750a8e15e024dc3cc59313ec02164e908e4730241f026f7916b5cd375cc98b7850ab8fb6a8354be82bb884cbd17c0968228f4daf19857256c51302f37e96955e96fa61aafb96be3e9564ba1a31fb4fc16f855005f296cec39f0bbaf86195a1dbd7634e904d4c32f0ef1fab8693b808b7a5559f6a55fe2783a8eef9d9223c6ed7e62893ae478dd93d0abb36e6da948e05768077d603e6e019b0a7d367cd410b2315b09343ee9e5c100d3026d9cc1afd4df11a42678cea31f4b75fecbbec427dd3ca496789ad8197119ceaed22d8ec254bbacd4b1460640f860cd0806882974024db5d304285f2cdcdfeacd705404d2fd335b03e9d9fac73a43d83ab9562a3aa18c7f3b3c042ba395d39b5e6e24f8950aa58f2cf4627e71dddf24dbea4b5b26cb2f8ceff93c71cd44a54eb52d514f477e9f6185177b673889f2660edc8a966887e5a7cf30e63f33ba017100c574ab6495225caac3f2934f38985b9262458cb6965c45fd733571f44da3c6d0a6c0f5fa4c8e687d5e20fcdbe1e574d742ac4070cb4e0954f01d6d6c7b244e519ec6de0117a9f477a37fa1ba41d822fa2cc608c939dedb39d28fdabcb79170a04eb57c06eab361899cadaeaa9a725899e3af44ecc34a89a1d868f4bee9cf51c27ec1d777755c9c7585ef59a2120a480115cb43fb0340dacb1e0709dda7d631d06339243903c180c8c0ddd3ea5c55e4d0b3670739a72e265105e43b0cfdcf0b02054cba2139f58893fcb95a82b85a9a6a12fcba2d33ed797c3049e0d8326e384d16b53de2ccdac2995cf64986d40d5c7cfc6981426f84dabfb3c48fa18fd00fe24ba9fee64806927264dee11f6c8db1012ec462be859a5ef28cf2669841dd8408eca5888844303a66806819ecbca3f6260f1bf20e14a314cd48cd835170795d3509e8032d57bedc30e29feff9ae9e96cb5595f0bc4bf9f14c5b3ca291ac52169ca4f5a980c381d3a76b65db16c669daf421833ca3eceb6ea048fc709fb8708df5915e3338d1791b854166c46ebf215327a1eb96f8d6555f859000462c69439648ebddcc2e0958aa3190d85ffaa746a37e7b9daf40ee38ab4901a1eac23ec4d4bf9c7e4cd155f528511014696fd14d2380b73bed2137d24a757801cd0b53b767b65e09bf6c4e6ff592d0af8e34600449bee7c73d1ee67507b383dcc52b3f10fcb5185fe2b8f80f5071e3a7e68e3f9a455a1e76a529121939351070de8e8251418c4e068fbeb3666520bbae3c3ba7165eefb2ce616c9a771ccce8f7a9376078fa218e6c6b7f6e3af9b511ca11fa007e1b99fb2858b395a6d431208c20e3bc165eb9af4adadb180fa0da14986ef74552a8cf8f03f6c3f38e496befd6b9a8aafb70045d6d1bc7569eb28613104467d90ef9cd56e3e2869424eaf6ba41e943c3454f417d02495eeb30da6813693b3356f1ef018a1bd2ffce080de721924ae1fbfc67bf4c6f96b8684341568c59a0a256bbf2eae2a150037782ad42ee2007adb7f8be8c640622fd394f8d93fd0d58754ecb3435b3e50e7547cb7344b2b4624b5fbba13f742f7fb514a4b6d909b369649ff120d671660cffdf522b655ed1ae273e8a2233546893295e87b8616cb434aa5b2770674a5ec1fe555e3cdedadda93f7ee887ea108478a2079661fddaecdd3c48aa7cafd1a092e833ef3948315b5c1c226f360471f26bedb49bfadc678d9fb7bc56d09c6ffb756b9243aa0ef3dc38c99a3237e98323e8561e5ef62edd5b730561d3fc5f7d80f69a891f0a3029795c55a694d255a5653b7762ec0c01993e62f31adbff428db1832f8b40206e7dda8e312217e1783cbb6fb3b5e83435673c59ec29ed0c151e4bef043fe647d6ed0c1c1ad910c80f9f1c0c202ec2f2fca59614981d3b95c5e166955a67b558deaa7ffc050da59ae3b6f172ea9f14ca5db30fed3168fe551392a682eef3aa30d1dccb45bef7a9a9eb3de7abe818dfe3501a78b096f6cd9146a50e7c5d456754a9c32030bff0966bf49e5d25622466aa7331a038687e7a988bf93169f6815e295534a08e5072b39ea2ea44ca97de71e0b3da15bae0e9224686c7d3af73881c7cf04558bd9391a70ebb8f3ed5c86ca04b04d49206c0b49a35be25f86a851002e4ad4db46947c4ea0088cdac2ed85018b7db54c8a5d039c959a1f4f88ede2ac8285a25d2194d0881b14834c81a13b149bec940581f1bc8dcb50d9d6bda3692e61d9554016b14deea7634ca50d942486114757aef83e68d3b1314bd8970c902dfc592419f53c797b83810e0868f41c6a7e033c7adf0398fcda0b8f17a4b517992e742feb95aad098bce44adce485c179828eaeb96b9f590474ac4857e813fb3b8e9101170c6a59a5d6c4ddb6a585b9a739dbe3d72a85d642531a9431bb4d453450d24a27679f04922281fe5c026ce2a7d5e7565884de1ce99122a4f2d19b3fd5332ba9823c60e64befac82abb57eb884504ca08e8cab253c9ecc6154a63313aa47b58dc7d19e9e8b4a11f4fe65572fc86622bd59b79cd91e0bf2cac149b7eb779890d84094f5df4bb00590aa7c935f016697e5d8fbec68d78640ae8018d9b06eb2b690a760a82d6227fc0bd086726b432b03fd0f08dbd3e54636d68d11640d340068e2733bd176cc52926b9906f395bd0c2eeeef88fe61a01e358710831a22fd0ba1d8b8dc5def05bd71453d597ca49f9a3311c06940d2e20b5ff7b380ed5418780a8ff95ddd2c5696c65b2289c512426c277896c2ed124d0552e583135dab9e606d3390af6eecfd88915f6056106e0651ce7944542d250dea46cc1c55f987578dcb1dd19d18b323d4915351b79884f182adf23bdc69ead612d6e215fe463b803c06c4d1fdf1e912fa00c9829bad2b067be9d34145a3b4683d7e5ef1a9fbbb7a8392133acf469501d4ba732d529e0c7e8c0e7341663c9824dbe3d431771c83e94e524c3152d7fbc4a56f2f0f86d0c5eec621ba478a032871cd19181e008a229e8ec11bc0b287899b39280af740245c54312307a2ed89245cfd22e2de550d7a8c9add7c18457ddb5c628bcac80f0209a9f7f63bd8c09d05b5c2234e9f2bbbe4e35fe6a38b96870f7dacaeb4cccabe80c768d27015f9ce3ba35bb2cf55f3de00190d07b7f1ae30061d7cdfb4e1e0a557bee60f7ff23fcebd2f16a1feba97c25e17c85e01383f059aaa3886ed241d2ce9330167a48deaeffda4baecd5821545c59ddafcad47faa7ba2238a76207cd964e92438d5321615ffcc6cd28bd9f895facafcaa866516090d7b047294cb33af7c8905a2d400f182c308a57ed8dbc306360f1ffcde721ab53cbb7a389b0fae25457cfe014a7e5c57e64444d500743cbb28198c625229e273513f97d495d1f1a64e25c9a25846c0bb5cf0a6c8157fde95cd8e71ab083db47c78d74eb78c77d65b6a66e122c381ab4eab627d1152cf66e58d1ed9e01c339f4430a6d55407ef56e964559a3da27b7265d92b64b7149491ba48753b6fb5aef08eea2431c3913f06236709e685e6d29912baa50b085704dd66ab090b9633f2236e80d70c3916c32747ae3e78b24fbbcfd34eda1dc27f89279097012dae868382dd36330b13eaa4e905a746c78027d3a7faa9dd0fe1d41f5e0083a9f1b119a9d06f203c469effab491d3e6b044ec165a6c69151b4d92e617ec5d15f50cf6b0a0c3fde0d06d1b27014908de035bbef5852f335e20cfe3202c2d51747539502858ae31309eba0d717fae7f84865f563125a2fc3fdb4ffddd57dad8024db19c659918593eb64bef385aff4b0070564ee2d056d0220abf469f24feb71d11abb89aff1f90f63cd25c5acde92c12b970dfb67af8c67c0ac965625a2fd18de8e8019f873b54c7e150d6965f40051ee819348f046944af9095dae4f984f6e350740ce2ae669aff75bf1d270a61ddd2c5ad224cc1092c98f7064230d50bf9830277e71b51868d5541f86f865fbb8ab7d7feaf68b20d9c956490d93d89b36a08e0ca175e07ee06003dad1a6fe175021488515a22975fa3e6cf57273fa24031d98a0a5956480b83ca78e9f845005a6236c7da9073e94fbde36b9bff29b343303b5d7ee5f94bcb6092639b10b2d6e9bb2d31cd7bb6eeda2c92d1ff0e7177f65af7f9c0803735a70e0dd161cb1225e0d7dc1dd807dd04577a6230da1b66e77cd92aed5b14d85b1cd2c68cca02c742782d5ca9f244e8697c96b1f0f40ca5124b6966f6bd0f7479038b761a0aec595cb5fe2baa5f54bed16543e2ae8dc89b9031fc4c85377918cce531a36030de0d65773380f4f2d79aaf55b2158c2b1ee14d2e2ade8b869b986174dcdb62c078432e5808e75d80bf05b7197af5fe3cfdf0ea8371bd2421c35ed93dccf2327b15ebc45b59948907310ffe198fb85adad764ced31d7693c2430886351c7d1613a358b8f674d35d25704ef248f7f899c052e3b76b1baaf0172336f995df57ff7fd3f18c4f15b0fee624d4d51bcf147df8edba3647a4a1c1acaf88744e40d05c56af113886d6fa1224a65ef624662a4a5d51cfaf9d01e988b9457ccbfe2da44e6e6a7c14b5942ebd998164aa8992470394ebb7ba14a598eeead8b785fe4bbfa94d26b101d7c7a73694537e0c7d29dfcc640a51eef97dcadd1aaa459f4d97ef5220873d4eb30eeaf75e8f5e04577cecebaa11768b26965a960b7d28b7f48d96d1583b5ed7f6b59750c50d346b207c8fb2dc1696aded5f249b4ee3fd032b026875d51b818f7b8946ac39fe0efaee8b5fbb834852060d1184d554e54ca797588e97c0fd8f1124ea0c818d16a7ce1ad893b16f15b62961202f9ce1fd0db74360861436f43b4675065b2ee1a8fe64101b8a4f71ed0cef6527de5f0cf95f7fe5f80ffeebf97e69a4f7234b20116a27febc1622b9755add3d564e2d26c762f79cffd7f6abb2ba5a221d7f947bdd3ae71a346248c120a08c3e6e495bf318e9c68f15508f17f916e6081c40c50f48a740b01db5cdb116d76e1cc82eb20065fec47af9a318163018f901c4eb89d80834ebe63a1a973d7f73581fa78346676a34c8ceec768d1687d6866fdf7b5946613aed6160fadadf6de7de2e5f1f4ac0ea20c6c2bab81273a2e3d77cd6dcde584b20950a610a0b1d030f835cc1cb8fc4b75fdb189eb40ffb062de4cd3ba66488683f19045fd88f68e2314cd6b8db089024011818a9106ad7af18ae03565838f6515aedc02c8946ebd35d305a35ceedc0b7f5337e40063ae0cd53633a171e6d96ae61a8452ca96962be5fe8faeae46a240c41e4f81cea66870424a0b044cc17faa8b1162455d1511e91ffb3ecc7c1a028f96de39b5821fba399dba86839c4c75a6dacd1fd3a5732385eb477d395a99024090ed73d03c6a3881a16a5bb1bad67c818ec577b1e8b4c8fc7ea2f46e7ab5997ba8323aecfe6273c686e90616c2b0ab31e5bda788bbf8f05e5873db6c6a3e011b0c3b365dbd3015bc48e5097045931094578e61125e1df3082531985e85c7c325c3094b992a9d5b23c9effb77d5a9071a09ba436665a090fb3f5753174b39519e4af0c84a422da40a7017715f97360df07635a20d12596f36c19668c56632617126d0322677e203af09fe700ee33fabb9c5101ea5b07d30201175aacf0211a0b1b4fd5c8d68bbfd9d2b1ebaf3122aa44289ea89839619eaa7d62a2f22571cc452988f2dc4f3edba7b33aa322294a00458d357ab7098f57da68183efa5cbe749aa414838701c08ccf5cb1b47dd0c8e715993016b65ade941367ecc864304bb181db3954b83c509ecf2ede789f1f6e289c0a17a200c791a6f98e6dd2e35edd93880e1ea52b487913bf42e0ae51c49c394c89f70053fa87a75862137e3dbfc2ee8f681f69fab7ea339f01e53bfe2d5e8ae498ad992b62f0892adf01bab6cf2c1505f4bd0afd3cb9c6edca8ebdfbcfb6b87340a220aaf03baa9f0a1376f907e431a9a021b8d173ce33e98516a1accf2776b742e3e7157bd31c662bd15a66b5a8e2cc6f631bbf7f36ba0e3f5242ff7ddf42691677513bb1dc99db3e6fb5539ba648212a38711cbbce41be2d9bd7615e3052b67e28b510a6eb824f7e6f4e1f9a852a15319cd72898484af86a6a2c08921eb3a4c90d3bebf18549320838cb888481e8d1112ffc0734425af9c6aade36dd84f518594a23ef85d05d23d9ca32945e42247505800f33808375602e456556690f7db370d6eb4b9998991956f226905a5224aa3315f5a0270249a8eda98bb260c0cc7b70eeefd8db907f6bfde8243ca089b1ef8783b29909b389f1cad65aa3200c0e3b7a44d89acab12ed8351fcbb193fe710b212a1315abb970f21ac05d6df5a0ca561bab13302511ffaed560f45789b9c6deb6a574678dbe5eb94ba1e45cf0d9328170138ad671bcb78ab17eef7c3b037d02838aa68d7420e375e819dfe99fec0e940bba7f56934b3dde5db289b819974924a1766b44a7fef31af9c91307a3b1f1389a3f1e14d1a19eba21a89567d2734ead85ba87bbdbeee379d908709c763cbab1d49adc6bc60ec0cc6c801bf6957309cc31073c04251c3b05e70948e3174d101db08aedacfc863c0c18ef68b880aac9555ed43c3bd9a2d70aa3f1422241fdf9dcd6341380d3fa29b6dbf06b14136031b5e5233849558d7bffbfd2b65d45e24610d9890ed2c263b876893481cacf716c6f1a827ce2fe6c990e812a76f5cf8fd30f46f289efa063020a0c1aecb2d6b37cbd1b7ae6fea4781862eb58954174955acf88bc1276ab34cc35a782913c8393671a75ac106aeb2b0271cc75d9f72daca1700bbb35df7e8199e61dbe9953215df8813d119535f27080fb4a1784bf20c57bd9ace2a34b6e7c0f1e957446e8fc28df25c75d113a6704bedc5024c688deaa2e1c8a0ced03f0465b94184fc8838110f146d8e1379f66b254a886a19f15e554089a307f993df575a3ca7ff7b3670131c6c8e1d2eac005d7806048e1d52d7c2e486c5e129fd40a7b8746d10527506364cfbebcd728fd58d3cf35662e1a1c8d2b6bd3cee45cb95d16e71a646317546848063a54562ffdc7643cb6641d60f91983f59757ec0fb71cc5a151019392752dab60915e83447ff4ecb9eccdd91a5677181d7f3a56ebad4742e04b53114d46e23d3a72c60f863e230e4876a6d01d48f6dd71bc032598bc4659b4ebf6d1ff34584ee18457aa1ca1d46dd997076e53cbfb5c14f22bf51ed0e9032eac7eca3c428258ef94006644982806e6d780bd6361d28847c973240097b8cb876a4c3fb6db2cf13755c4757b24c959add04dbee793b569623ab190876eb7134a611fc2a8cff4493cd4b4ff08348057ce6cb867b2f92f1e3dbd99b7f3be5193436e298691e1c84750dc96698608a46144ac59a6aeee5c983724c32d492c71cce1ef3c7a55222c0399204802a7ff202ecb547d42c811a5f5e3b72d1c471a77e7e2a7974493de74c20bb4f76c4ad48cc7c701811d973c00e684d43afcdb5768029b4f3955c7016687171c562e979a57b71db03ed04fc846d498081a25abcb1da34cdd231c5681ba2dc9ea3c58b109bf1e45af520de02a72916aeea63bbb18f05eaff0043657a8020aa7df73218c6f36eb2c91d670c87b130cd3f7e8c72efcba395020a9cad2b48f8493783a84c1f08a102156666eeacf50769fa6e2ce1a4834c45824ab6b58ffb767cef8a0bfac8a018bca415b6cefbac82399e8a620e1e8f5344eac972cd8b5f18d9e1c6dc404e778a70b7f4f864d25bdeb00a612b23403676f6631ca0b60f74fda4113e7dd0abe1a536c7665849f357890d797771eceaf407bbdd60e9569e1ff86aa8e70655b7922291859036c6c43276be4c6b2cdc887fc20ce1244c9c4b9493c443584512914ae60c1a2bc45a5b9633ddd6b44e4eb2383688d25c54cdb883f9a6f65bcf327dfd650cbb7f63141673541b7c9effba82ab77e31115c673aa2e8d65a8a76c6f53d8469b6066c3652156b68ed1c262e41fff40c7887fabef0651b03e5fa286d7d3edb6a9c895ba94bc33fc81e6668957f2102109308e97f565ed64f33b872f5ec52d07bd19f7787134ef6fd4636d0122d25b7a825bc0811a8a275564bed9812aeb678b8a1beaf24b17ac32df5be1d10131478bb60c438e01ce93f6c087e72544e3460bcf7ed20a166c2c5d529adfeaa8ca8f9cc1ae59e701b5c60d753c229d07fa56571adc8bb8c3c6f8d20d0b8c692dc136b35df1048f3e2f021617198862587433501815b098ba12b1a47f7e6403a6d0f57dd55f10554cab94705d95911aba787a433679d294adee94bf3de4d6b895748e824864c44673b73dd13838762a551e2ddb54a0ed29fea3cb03d18a1026ac85ed99c261f521b062f8e5d62f447028413564cffca0b68195031fe495951357d8d5cc7513ad32a046e38e36fd812a5f4784fcbcf05946b97d2734a9d161620e839fc807aec39e3361e6d3b51da0c027e314a5965a88a25e727f55cc3afa097f8064fc170c6ced9387a5edbc5c13645fd9602034a91616036792f31ad46c35b59234c6e5e2f6e61a6d3cf889243c0fe944aab8aa330d8321cd87888a1ac7a186c46308565c27123bf37969c5870444dc5b0d6dfa87f4a603bee4f20f397f0f98a21b419193cf086cbadc0776312794b3b91805f22104bf247b5ef2518701e458e3f982d1318fe4467927c7f9d0e58897b95f1f38fabb7f33f4b729e13e39e89fb41dbd1dcdf474215175c5eac4d34d4792797b82ee997950a80445856d10ae3c97ef383f6e9b78f812f509ba5952efb7732dd257c3356f5b719c7dba77d1cd056553149b3a967fbcd4a102a27f13849dcda9fa595b490c95463069598d179a219903bccb73e13413f16933436699e231eb702b21875e2d24a969c94062cf74836428553bfd4000090af1f0fd19f4a8925da8b5ab069c3092e68b056fdf8d19ae06b4dfeb148dd32a001170026e04a8eb610adf0c669ede6d325e40e33a4f53ad2ee731c96db7d26885040df385c2af61e5eed900956d745db6a46e1e0c9a6dee0971ce2445c3d274abeda0f049cda79cb595e8d7006c7c1a309cdf2f4dff73772b1a1b9427d4417d89b9e116329082d5489c484846e7b915f1a9b15cdc62b2776260d8b948a9e38fde6566f00426068c64ea368751cff507fd0543f5c1f08cdbb93b711236417e1d661242449df364ff1f962fcfc98811e1846b35e003c13d0f09e7c648440e4b765c064213ce0e31154841c7271096979a219dcf7292540e02125549320c2bb70198942b198e3eda290386d637d9e877338d1a00debdd29af571e9137cb386b687ff3d9dce551f502e2ba4dffc1e4f2ef602b2534094f297d7f60733b4270397f13574208ebf2bfaaa04a92163c91c9a03f2e0715e96b3f7a232f89af1fd33168801dd9bd93375851eabd080d2e7fcbab38b91901fad30f8ffe5853d4d1ab1a4a29670b8518c8ef33b4a7d45cbc909aca808f378802304327a4fed6953acfc347794edfb8a9fc5379ab957072b14e7d208f37bff79413aebc2ffdd2441225dc970b6edbd397145601185860c423bf3223af5bc46cab61e4e8348a3017f374dcdc742bb639c72b20a2ae703e5eaa6ebd95cc4935bf4282c2f59862f6f37a51afa1c9efc231003f1241ce8eec15d9bdd88b78af427dd64aabbd6b3441adc5d35de352567595f168d3aa196d35f6867779c8fa861454708a7aeed25fb4e9a789bfb9244ac4edff54ab87028cc77fa1551fcf49e19d12e476a83baef2712b68c952fabb15d58f6a6bca0eba1d9e182900ea5659b121d13d7c118fb838162dcddc8a0a44220b6f78b60fe876e5219d66fc5591ad1ab54a80bb499d568995561a6b3381b7ea1812d000f0abfba4ccec10bc6910cf99aaedf8828068d69ed06cf79b56b09ba98e5f871f68d1c08c67c2e94a26a763c1d2186127881ac4ebb4488c5689120ccbf25154ae91a7e925da3c35925fb88402bd5f19e7f10adfc36a6c73be878a5b6de9cb526cf247aab8f82a0a4f224cd91c7b9ebb1c92361ebba77bad4c3075bbf09600273e7e54b8336676dee46e5f98019cc83ad07f6ef77dede5009f6877a5b22413417c0a447d4c3ea6dcde9c53c3604cca3ecbb2e2e50f7407f7d699135eb25a14af1a7a9a26a44145c3ba100c9c0173b59a92757e3f637e6f6eec56094967684d2c24d008f7bb721996ab75dc8eecb3010ca7203fcf24ad853ee2a7df24d15cc259f26d5aee8131d4727fb94638d53df3844d5383dd9408057293a3554dccfa6100aba17eb482b9017b5bb99f3ee93b68416f6bec9223572aa740b30fa837c9400d36c1158dfece77c6f7c1c2b2e70c933a1dc119ef09711888cc568e11f4bf375f2f504ae4b9534e99d8edbec38680261a4e407bbd8365bd10dba7d39817e0d77e89359ca491e8b9ff601df2405a5bc874b7904e10b20ab50ba7dc0be347169987afafc88fb3bc3e57e3e8e2efdf7d7e95c514edbe9087e13b60d97be85aca6c4b12d96054263239d4bf0b089e31336e5eab1a4506feeeae122f491e95a018f8bf0b2a739cbefe575572de5be948cd2675ccbe021bda8eb07df262eb13c160ce76c852cdaf913fe5d18a422c0879461bc128645b34f50751ba46a9805bb6799707f1bb8d47b8fa7caa8c453f9bf972d6da8779e8d1feb14dda87956529c8a2cbf6648df52c7769df97508d31726d2bde2f622a03e1da900ba5cdc17f8ec18454b727d88ed85eafd3e50be9d75e767c229421da529b1af1f6269413548d98bfaad97bd60b5406ab1b1dbc0a590ee4c7dcaa30f4080aafcf8bf057a08ac5128cfe6e3e049ae5180942df2af011df206b288ef43a4f2eccb0a67aa66f760158c41f111f470819980bbd64925bb9cb598e02498c9475f9199722fd176babafa83338e175afdcf35edfe040163592591eaac710f323f4862c3059372a2f4d7768d7fbf6706fb0e9cdb40c9659e6fc92d80fa1022a7432b355bddbbb45ee468187866a0c181cdd8db701b4ed25590566b3eb97f8a8e49e66484a1a891b05caa833be4aa8c402d6a5a41130f40e3941f314b2575a6a12663e4f1c14232f09d14fbd4fb08ba2bc981f767f9ed7d8bb77ca92e0ed63d62696ea3ce1cc2e0260e8adb3822e6373b7d20d834bbbb365acf2b5629d15b8932bb122ffc1173cf5e73e6d5fa1bea39592e5654e462eefa23c854c5c295e84afa3a55a1703091c7b129b40f11881f81d9fa90a5652ff87ab4c17863b5944d26e6d197237cfee1f9fe7cf94eb40f6080a8c56fa635a67fa5f14c0a0ffe84eee1a59de45fe3f5803afdc859354d54d3772a89a677f23763874a048b29f8212785d93194e5b299e67e8b96267d12e155a1c11dff31643c4b03a50c7ca0306e3e7e39a2b8490ab29bffdcc805e353097105bdf5f27054a3bac7cc6407ab710a5c6db3606ad26ff304ef5d696c51319bb5696b82219f04c1bd57e2a6154e251d82a2993b69c10026869945ae30a2f3c41c4573b7ab8001ebab113211f7a606746e1d84806e04375ec8f23b58637e02222b7905dd3009005ce33738c07363754b2074a16d060b884f8dc6a88f00ae6a260fce7acda21ebc2cbd9b9e79b2a5efa01401d682e398e21fd972a0b2601e215651a4f9d124a49007547faa4bf7785bdd45f856723c3698b20b26e2ed885270cd186c833098955467210556af4ba4fe2ce3a186904ae7227b5bc4bb326d4d67b817ac7f47e51e17d4f2aed603d47e4af52aa0d961030fefc81069602ed41b4acea056ee9701851d08ad3daf2593b438cae7d457b728af07e0ef940a66b811fb4e76e416996a5b8787ab6a5c3041c3597990e049e9dafa51be089f9928735ad37fdbbaae8945b461f2f0056233379ceade2f662021ff5c7951db5271846925e84186bdd46dd8a2fedec60d11954cb481a5aaa4e9f819a3b0a6244bf303d5cc776dd173b1acbbb1f60a1db7a924c5033722e6d4505b0777400487902074de56624dd9f6b9d706bacbda731afb714a23b20576aa23a4980cb2eb0998c0715b84b1f97821fe29a2f68702201c7f2bb9cd645e3c17d43e12dab4b4e475fc129dc094a09daa30aa19ef6e078303bbae5f5200aee3f6a8ce2259156a5a16065e5bb6067d45a9f55a5f81b8dd9a44d5dee24369ae673ccf5450c8d608ee1f8b83159f4f70b280b5891c67b253a2443d2990d14f37b8b55897952cd975158cdc8036546ec5dabf19ebb621d3468079b2be7f41c3d4698c4694a45e43ee1231ad07df8d99e7111af6d491ef8ee9df36a93d68dafba8c371fc599427086f8b5eed9b57e33a1971e048c30a0ef4d5e37b04ce346f9e432c317076a9a40280ab69093dc93de45f280a65650e847c1310c567f89e7b4e80c05e3de16e3bb1b5834ff4125fe165393af4d06c6484ea19170f9d7b04ef7a5115296c2c8b24e93c511b195d7ece59ce510b53b71c00eec906ecd898c72cc0e7e7ead7570ba54b27c12b27f536753858563f67ca3cd511089d6d4c9b5000a04c781338abd8b357f9f43831ede027bef00be54b36f01e42dfae98a4cd00fcb5ee2d64d8274dc0d5ba4fcf507000ae86a9cbe3634a232c329ea8c3f5456b985b66d45b23bfd05729786df560e0a059f1af48a44aae43c800224692dff2796e9ace71aa908c3c4c832c5677e6b203827f084f9f9f454de96642cd04600ec6a40404e728c4cd97a97cbef80f24afb89ac43aed57b3b6e1c056465e6595961af451e2d8d0995397811f0acf9bc234895aace69ec833dd8cb6f678f77311a37baaaf767395c885d12f005b0932513e56b71044070d9f73fa0d160f826c22287cdf46a58fc46bd416769a470a09c108dc6293a1c7afc1f2bae46e93858b22fb0e0f464f520b501db4f1c755ca5e6033bf74c95dcfd0d303d5c3ffd4620753ee8605c4409983a5506a3efc9e0686ae52de32f047f51a214acc07f6bf6d942c61d05faec3530f0c49fa506fbbc977e484438dc8c284edc7693798d18ab86e1a99c6daadc78a2d40f2717b654b9a6d03d27824c26a31af1d8ff07527c5fa82ee83e4a181877270d8343c67be58baa6be110b2d76f1dbe879cefb9e392b5179bfce84c1b7c61a2bfd14a51607ad067307dfd6dca16c86a230f27a7ee2ac0410911d632e4ae43c6ef1ef2b3ef5a5d40be97a8f8c4431452c1649162c51051e713d617a6ab667059ba3c671556d167483f0fb75598c3d024fc87e5b2fc3406d822d8edc982659e5de83c8981d9a73beba3bffd6f82b82d6d68e62fdb9e813d7ae7dfda0710b8af205fe621a9572506280fef0683fd80a2d16641d13fbd6011d901fb7a8e9376c1dbad531f8936553aa5a12c3b34188ef8165175a2ff4f9bef3624c5d30fb9b282c5d2aa9e8ebdf9f9747d2a362386200109149012df4e97bd9969002cf46731d9971ad74062b3f572b5bb86891579e3ca9f48c615a3a53f218470fbdc08350632a0034c1fd197b9ecb6b6b8425e392906c4ec447734106930e74d5403edd79f56d4e1cf021445e0b4d80bacc15e2f10ff97b7c4234ac39584d8853063fb9c172d2b209d8af266c1aa6ed4a8565009daa2833456e1434b55f297046978a2b9b23aebfdf23a53807b9d78f49492feb9bce10349681fbce7e4a72c26ee39c5d494a106783feb8fd67c2e31de563e413c9c05b932e37fde66fa95eac5e5ff32aae5f593ee89601d9d288cf20bae43e52c69b8f47bcd6e0f54882f4420a57b67a0db91ab7a9673b0968c7406e2d48f56ef802ec90a74ccabb5e5edd64a5179876f5e8f6d1a5f7088e73ff1a6c7bd4e5b056ffd9d544a098ceaac68df7b7dab2dd1e90792948ddd5ef4d36db936aa4930b9b717d322bfe67a7b5188e01af1e988c786851701c178a43eeb260278ad0d665d474f279d0d55e8d6cd619beb7ac7c16b5b6a018e116320abd11f1458f26610eba2122feb03481c920e71678eea030d40cefa7708a96e32b6e0cb95d20716e522fd1b4c436878c3a609ca8b647edec4d2ee0e86602cfd98ba7defa1af6c9c7d4ba855e2490b36efa7200d4c294d76c8e479cc23118f18788d0a7833cdb03ebe667d7351a90008f4ebafba7536dc02df0b28372501b361d56e05609d02b1ee955d7cb8c3c3dbdce10aa6d8c3a90846c93e9473d9971c25a93f0730b840402c57e3fdf7acba30fdbc6cfa7e6593369ea7802adf36e505cd96b86ba6e13b460bc9c9ba471676beaefcbd667f686c8a9818633deb00c479005606704ba8d7cef56cd0e73eea5ff8bdb4369344c34078c095aac548b402918eb3617b2f8401ce2a2790450e5b132b1cadf669ff464a6a2b18632838d0df72de8022a3fb4419719ea49de4faf1740a398f970bbeb2105c3112aa0bbcb34b291151efadb098c950b1042003791d907e8dd6def1921f031eb3643c005bcf9c3ff992144cfce7c7cf767e7e4fdcf6b80d55c2e11361425ca47fae3bb91cfe4b52cc5a722217774f5268b77eb4ebee139c4486ab1f92b0ad5427109b9b08a5eac1b54924814d51b4a74ad010868087ecd13c4c6b2eb7d216a68d7b4f1488fe8a03a595ce472c3cdc388dafd8fbe2de91148aa320120f755fc59788f747c641ad2d31495653e5fea81ce759d732f4669c0895f2cb2efbb89a7a205c0304a8818cc645fd4df80dca8d03c3540b7ff79609b669df1351bde3ea94efa86fa57cc91315c2216638566b6e35c8903fc591840b43b60a8fde9a1179767a7faf3d4d6f21852a4be90c88462eaad9e65d4ba67c1212b62766fe906b43b2fedf767cbe5b592239766969144b7230e4e8400ab7c516c1639a8a7ea1e78e2ab5ab6ed033beb2776ba59bf2027b6c9414d97fc5a2f5bf8965b6353dcec520c79620dfb932650533623ef81880365b2139fb0fc429a402fc183a5a23321ba9423dfbf55a0c43eb856b6ac6b4bbe3b065d5fbbeaf9489bd9a5572dccbdd3a4ca14a4b24393eb89ed4eeaa424931da75411977d8630177135d5a853f43cca0eb8abe6e0a821ba64f628079db8deb44e35d19b41c090c469f6de0c35f6e6590acdd9b4547b60a89c5537eaa6906f4ac26c88da72215eebf079699dcb76d03a0500262ab00c3c24a6d88b838c573ea31d80c0e673c8f354f36f4d965045eefb495f4ca811ea9eb04ffd3dd30d631fffb91c2a5c4a5dc54a1f696bb326d7fb98c2d051ec24f5900e4b5f894e0a85ae0dd0c68324dbbda90b89c73e2ab44fe42248c037289e5e2ca64e23d8f31048518f8eb37b415e48cf4f2d3c3ac453a0572f19f1a6ad4ddb277b1b3a220b801e0c28bf8e591312cfa31bcabcfcb33b800546b451fdbbb900dcbbb6dbbd3fed51656f1ea468073b6705fb8af9a4b714dd5bdd693229e12d4c2e97483dabc49b6be5e3823b3bd437822cfab31ad01b7b28b8889d489d34808bcb65f2af1f9c04b9c9d8f84eb0821fe76e80cdbbed7de8840151b6c76cc183caabc726b8548388448e93223e0cc54477db9e00441e6fffc89acf35300b7b1495f0072ca1d5d58ef98c0a09b4cd33929ef017be15dd0ed083b93a5f14dcb417efba7e25bcf95419ae64d1ba5795896cd33661f042063dcfb6a3bedb5b566935f831ce1f86e664b9963d6b688c197794c75b2831bda15800d106de8bf26635bde91ac6117e23847bf8997601ed5cadf0c24dbef9e5e4f9857fc35cd2bd5958bb40966611429dfa2cf87d6e365599d574b4e6cd63ebdc25121133a9b128f76d43ca6aa26f4f767b1a8e0a81423a9557ef37d52079db1c504cc07fa8332d88f9bcb3b461fbd1e1c28dd5f7f783260a81206c12cf77a1397fac8b7a303eaf2b65e22ccd29b4480956c374032c583f5b371477094a7ac98bd1af068b865e8f4ebdb1a3a372c59ecaa0a02806735e61c000d88a8a3b74f4551f38f03ef32423a6dc96c0416932978c5498e7f012b13e6cfa05ab27ea18f8d0ad5f92b967600ffb517bb3fccfb9702ebd774ecc6689007abc7f39addfeba6d16ffcc8611dbd0fc28e376d72c4e700c142d125e97a38bfde4ea93b2f6c4656f553e03fe2fd0a575fdd68abd2dac1b8125e943019011954326fb1d0a8e8da9f3135853c57f6d1c6b3aa6bfa61d503279809473a84bd387d200420681dbc12f536c05b7586cefdb39db004468cc6345c8cfb2dfed04e1acb436e17c0cfe5a02e74970895771529dec94f41561332bf05124a9f6b25dce0ef0af9d00c4060c06d2d5e1e3354769f903dd424f6332be06dd58297f90b455c3f7c1dd41536f21d92a9ad715ed8b47de5b5c57651ba1b30439cdf62f9bc073a99c4e9211d9a17aebd3a9dd63dfad735d0ab03e3258c25bff26b9dfbd1c6e9ba48f902fa7d739e2192762dce855baaa346d586bf52c47d86434869baf57d7226cf00608ce60b66b34a3c4fe27fc9d077170d55095e636f5ab228803be4963715e204983d250b0d9b4d78f89fb49f0375a02ef2b7d05cc0a3494b3bb49fcf25a131690be85122c17f7d31b3d701716367e0be399c6b338c066549e5e006df9b3f0d408a37f49fe1e5d225fbd29a285d8c83d2bbeb8f7ebceaba6ac2f8ac5d1e37577c12927d49f396eadd24183b08f8d484a402837b620b5e1592c7db4c3444f2ede4ffd3f6db0e7ce9e6466d38dc46c8a69c8457369a6b32c4b6e3003c2e38b7b37d6989f9f475087c86d38192989ebf9faea36229a742899f1b5b79e6b3f16b7f0ada0f4debf4e19cca88daa00e78d47f15f01777306cbcb59aba1ebdacd96bb2b6fa6dfa7b44f149d1f7f9fccb251c15921eacc764bba035b0b039aa0e0f3ac85291492a81aaccb6d9c756f0470d452023bcc62e37cf35d0e93e55c57f51b6432175c5d8b76ff527a9ce5c9aa17fbb1c97592f51da16b09795aa325f7b4e66896752b79103a3986b4aa765560906f7d6f3b51dbe29484e188586541e657ba06a78d953fcf1f0be80b0f2a80a6e2e439fb050e568cec8994a38a9ed32126e0153716a57e704aa6ea66af232310710cc55a2ce4116ee2672bc3fab31b3a72abef33c76bb1fe80de6520a1688c54f70c7d1e18560029100013db724190cbeef4e401236ac027c3a802bea0b545799699d0f1cafc627d0c52c87cbc4f0de6d002bc5da9d5fda65be1f5532e2407375de6ca20e378a397e3025cd126ccdaf20039f10667305c6ca46f203b94aa706a0edb1a440afcbcef0313824703d9ef34f0dc5186665d417e3dd2cd635e004a0d6c7ff961c6bc543bdb2ece6f99e11b686517634a4001aa37d58e9d385027f7b6e3f2e47a6faad866fd548871e092ad7d8146163e48c9b7970acb95aa5c25b102b7b0c3ae14eacb904faebc93c60ca437c6d2c00965f2b80165e3503ba0208a67b1a19da6bceab6c6688a6a1204cfccaf775554f8af094ad7c34df71265606a5d78fb619fc3850ba4cc247aa0d46b0468d2bf709bae0f3a37da3c59b6cb651f306e2ad2084d11183a83ed297bfb312f85bb571c85d7b7d1e2f3944fa37d0882fe9c84e4083b6256d2fa239e987f19917e1ef9b800b5e899f04831dccca6dc0c99c2d843cf7a291433f1f6034d771db8e48e7a72af114b494b16e8092eb9cd68c371745fad22b6edb49e6e79d79c2fdd68550fd1640141c645433b700924cc5e2d26ce064d511ee730a88dd1c70fb02f119b81b0d49a1e0a0419fff3fc828d1db0651ef2c362b107c5d47545af5d049c435418f57096a8f5368e02760195ce60773859af2e9d2462389fc441fbce7717bbea6f99797606b114127effe606c6a41c5151c126bf72e60e7ee8a9e0963ad6be4b1b92c9554c703b84d22de1c20dea2c4e9c5e4f47363059274ce2d71ae6a574bf931e2a254eda88b9913cf42d54e282d3a187010e1f0c69ba9f98e3c0c4c9ad7a36888768dce2b49c7eb5e35f4231dda2f9938164bd6aec14e527ebf66724101d5e4d639432ab3ee733dc6a24d81f99994151391f85fc2a965f55b369d84c716a1967b7ee8a25f26ef135e9f24748aaddae00839d41dacf4e460eb15ce1ea1baed6b83639750f078a5764da077a6a6c819d4b418ef751aa7cdbca88aab1f42cf489bc7e17c60c00ba13425c927fd00174d636926008ef61fb044d051606275d6fcdabe166f74ba913fd64cc53d75e06bbe3a7a7fe400ce21f19e62b4568b7621113b88cdc05c4af57e544a4d295ca54aa56e3d162560e36563e06db97b748ace4b544ece0a9cf4ea728663933ccb50a33183bad716a62a503a7a76612230ccd3bf2b92457bbfd7ba15ff9df4e4356c0941cfffb48eef3defe27aba2664b5b70fd7526acf2c86a2d457da9bffa8e29808402d8119e9de30b71dc3b2ff85a0a5c5020937b06f5ac078227ab1bd75357cc7e303b0b3cc386164f85f31f0cb9304dd9af93fc0d2fe3a46ec32d91d8958cb6d8c6abae113887da60ab54cb215a88c109c8c41b96f9f9a562b8643fe701dcdde08cc826a47bac68800af515b76bf4512c6f1d47a6af838ceb77607850bfdc5e4f33c317f8fa232c773d32c6ef17dcd41cbe0751b00901b504593e2f2ed487b0d72d0ff047de098b5dd4c873ad0081237cbc33a17031a67139c25d705a45913c86eb38acff496aefbfc5e9d852385676616faee5ff7d98dbb4e3e79d8046b769c0d3deae297979f8dab9c79ae796ec6c6dd7cf8961b5a0f9436ba4c4b258e2bf21411c9ba1f507fddbe7029e07e1a39c19acb2f4621f9b90900d57c06f1d8366c8aeb7c7a8d86ecf43e5ffa3867decd60abf7bd2662efffdafd3419990ba3c4b3cd54a0188ea2e34e9a2602f882529e383ec943e648f714fcc996728ac14312284d759444d39883ccfc1602e011324a861c6d71d7dd26673910aafc1816f84076a5edc01ce2517b4df4f21bd77f7bd93a18096176dad33a255716b912151ae8ef84c1bcb4cac9831364f5f67468a4da85a7d6b6fde09ed7fe65006751594e9a0f0927a70b5f6f590d0ec2103a83f7785ff23d5a3bdc6e7aec38a0b9733c85630ecbcc8b195ffd892ae66b4c5cfbc836f05497db76bd43d6fb0e309cd84d68a92fc9fdf39ef473b165c3fcbf646c103e09253afed2837cc35386b61ebea1c40de369f85ad4428878f815abc059dbfbe2e439fa5a25bc5b113b3244acda4f33f5e7f047321da3ec2bded6f013d3fc0796bd0b5f59c10f330c715abf09197519de31136911068f7d698a257fdb6cb317cc35c26060dbecb9baa395ba9acbc5c4430e5ed8005fe4a7448b8c2b0b1526878af13534c4460b6cd74dc1e79f8a236524a9f5a401b094c1fc4680c223c1c686a56fd330a440d820ea3a4ab675cf47d7c94f66354276f415af197ef6c37f7ce8793859b399d1c180de246c3da606ce867497a64ecbf5ca8dd3cc9e58f84859a8de16a56c9cbc759281478c169a4809bee6b7bea78a5e4df17f7e2ee10067b0c802bd6c95fb3d707d2b95d4b2150ef66569cecc93276c756e41ec88e251e33d2e9fabf134fd83888591ef3792c20e9623b0762a16033465c19abb2e724ff21cc9aea40f03b2f1dfe516ee1ee64e1594d9d896ec6d19b43eae7966252a7f8da6eab1ba75d64338b0306dacc03a46f6d31b738e0eedc3b899ed957de6b903e442e28b701fbf0e3ae20394ad400d1f11b5895eff466b08dd5a2cbe2b29101eb5d021ee9826eb76123e9c41a69be2e0ca6dd52922d569a53b64c3a6bac3cb6ed37915d4e95c14b1296d39e5b6477fff42319c76995c4827a3fa7008adea4d845f12bfe2fc162a89ed9825e042777a95a224ae6b882a1b7481deaf3abcdd35adfc5c2f07a22fa491abfe8d5bdeb1ad82d91270f79092e281e6e0b62e333ca62631b0d5b36078ff8c24a0726323e11141f43ffa095b8573b4c21ae434357439fd985baa7aef422f200c424246d340521e8d97763c4fa14c9606cf0baad902b257f7ed468d95eaa92d749829dd30acae38ef4a0196889e092f4f8d0739d60c7d1d5a5456bb495c08c16ed62e462187fbbe847e22b643f6d4b56bf683e565a0495c089635710fab56624818b1de7227641e33563e85ea6e4e4cd7c8e0d5de9fd46991ed87269ef10e2baed8c882f7249b1e6231b09b459e76df637e9f01f774848a364ab271ed6cf6dfa865450549e6b2a0b5e384bcdeac8ed56f47aab02819f452658bca506b2cae378e6926b1e692e9a70ff34c9821f44db1956d626b3d8a8a37f0925ab03e6e8e7ae9be5f5e7ec1fb27aabbaf7f6d6f8d09161cdd873b2e80a7d20fd8529e26d7d6da65e8d10129f25cd15743247a1de65e49b1e1c42468bd3ba57d8b493d934662220a3eab4ba8815b93775306082b683fcdac74031b457baea20fd1bbbaa8991474602f3375dfcb5a010db729c906a843e283c93c2044cc9ebdbdd9617a765c04e9099a0cbf4a34fc60a205143bb73817171406c39e0154032b7f360412cb54c12cba5ae6e460ec8a29b6b480f27dac6e4f06369a6d6c85d925cde403e6dd7eb8327a048fc10dd9bdb7b16cfcd2a2327e60a98b1e55a35516eeb0cca03ccc24819fc9c254ffe2bdf1e98f869769bde54b1d2c09656577b76f61547bf81443101f03dfb573afdae46a9c25529e5a1a7cbb903a354a1b5e2144b24f55fed191b35b0645829b391154384e80b6d8c7389f8e8fde90c5b87a7c298634b570186d6e22cf4faa67ef1b84734f4734dead706e57a0343cc1485316ce3495870ccea01a5693ccbbad72025aed91cbc335e54560541243589e7b2c7556309b1ea8d228866812c9ed8f30100a8be4d7232eff13c2c72215b8bd45004cd1d574b22ee7b84be239c5d2285871934c28d63c1c226882072b2d9b5f96a38346d76f2f55a16bb1d06e804c40c8e2cd9427ded21ada5e28f71ebf25a48ec83d8327e5a5571328bb77cf21c5f9eb4a332850f5db9d8c109debb120990b9a54092473bcb056d042b354613297c84ec2dc21b726da97b31db4a3dc02108bc53f94b2249549c0a3d28e74f21e71058f533c5e22332212ac1d2abda120b7758850ce364e2efba07303c1d1f78d273da602d4f964730347cde829b2b9bb710ad357f15aed4c2eb394ceeb4534099d5cebde1db1980340e9bb7053f814ec3a085cee100ff5259a6207ef2099ab8fb7c32c88006922f69d7fa07e13d357c7a10191210fb813d6e8b1f544f0ef8d46e44d642831661e4f699b7c345002916350d048ba5f2e1d09f8a507de87844eb161d17ae121ac9c12071682707331c78e40dcb4fcd5da37ff273e902f0cba948ad15c051b2078ec84d4e4d48092ecd9c5fc745537d3e981550dbdec7a969b14f040ce82c31fbff2b9ad3973fab00028f532d837d97fda4dc9858b14a61eb72f7982c77ce48261a2141ebbfa6fac59b22c7b146837a08b4f8a14c9b1f5761925e32c7d08476d376a16b313dcfb8bca109d0d7ba7dbfd9bc9f5109f6748fcb4e413232151cef15915691696105c5cfa1d8bc2d85501e20e3f3ded6ffec9ed4af9fa33efdae2dda07c72fa09d8b81f39ba32362b2f70d7f14456f27efe92726a18a3418f80e746f893714744b657780bedfb79c07fcf2cd0408591f7baa4f93fc6b4a8e38179911004cf173e1c44b30e6cd75fd1acc82bb14eef2221c0a75482569d27d0258ea4ce9413fd0026b136b3642a836a4fc50558f7f6414e83133a98e2c78882818eb9e3c62e5f1f550e16e41a8ca1d3514e124db3bf5dee794b3c60afd624a1c1dba81c4c6fddfa513f3a247db04b3171f525232408cff262fc49ddd1cfe9a9d822e2b83231f1b33d0d7c9a2a83df1ed8daff25cb83171957bbe69286511d499f09830b1ee94a8d927644bf8f4f29437de7ced8e7653d1f957dcbd5ff8d7e10a441c87fcc020f2d2f5e0a1c906d1405b71881a238108700cbf6ffbbccd2baba2e9d14450cd985b3b77fad8fc9658679ceeb6a3dfb18a0551f43853f418d2fb5e299e8545761e504fa54ed0bb553531054c97a1ef9b8ce7cb31b259f9a4970daadce45999036f5a7871bacaa67a0a0641601d1c403bd06f146cc4592c80540b97a90dc521e6e5d944c75a19b94f3d2808db444eb3587f420a02ef5ce22d14e17a6aaaf660eeb58d6ecd3369820cbcfec99ae729f3c334a0fdbae10b55278b162db687662f86f0dfdf7e078913029e64b192c89f78472c30552696863d2244e6eb1606c689ce688286e35e1b962a05b960292e14e1834f7353ff14fff8e56ca11194cf2d16d35c5b7500a6a3be022e435d28b5d178df9b31ffbac8f30fedcbc796a1ff8f9ec3a2c3d85b2cf09e19fab78581d29e86dc42346a901383265aa3d9e3b786c565422a691232ce5482e63bcf85402545026d873221f2c9e68bb8d1936e3e0edfe632d261ff751f677a857df737393c5c5f8faf04389146d469b6192126fc301177d45b95996e37054ae9e2a1b86a5a925cc56f3021ce7c4e7daed0c3858d0c08e815ad956fbe6a3f9cf3ff38a1c3a4e08ea65fe969407d9390b9998f1fc63b2d722d57129ba1ca9f4b51161192b3d14ec17d84e33034d543a0094cc41d9c20fefc840dbaf36ba9c1a3b19ee212283c1e114253bf48427222ae50d3087318bdcc0d90524ae7311c63fc9696eddb9e3cbd760cc2b3ce170753ba9c9e485fe721eebf6e3e8580c4757b6c2b9269d3bfe16070b1657df2e61d769b67f7bb90d07a261549063d4c840e7b1f1dfa22b1004435af99381598077a862b3ec5d185b789964c2bd261207c5848c401e933fd5f679b87d82643010a0908a08936e61472e406896ba78fd4759cf61037ff24ae7a19d5158b732d1b55f4940666aa016d22768f1877b6812598609c7f095a1f2371b66a9aae46aa2db45c6546d9a6bfacbee9b4ea21bf6ce3776ed217b20ff04f6bd0278e77a3ea6dd893afe406b60266e12d58ac99808fb9309226d5b8c4112da55ac20f4bf4b4660b3573b2e6b22a76f19b7ec1d349cea4f361582b612f7aeb6efe5bb63b81e925345e4fcfa28f9e09dba6f70f20c5b63551d7388e2f38c7a4cc51f1fdf1a0098a1c08ed809e4e150a98761db59fe8a7a089bd83e01190511f79a6d97fa1b47d2f0c4015947b01a5dce07c6cf707246158948db905f34a7bd88fd45933d63176e4b874a4b312144fc9b8676fb8ef7defddd90979ce30c3c512827fc77a7bb203baad2900ea4e980292dc8fe0a7714c118642ae012560ec905658bf0b59e23976e72d6f0b8ec0634e723fc8fc32a135428f2933071fc21dca66d6ec8b0846138dfd778649fbfe327b7f6ed86fa58bf0533cb83731ae2345a97983bc82c43e7e0d698bcdeb16c4a2dd596f10145f2c18daf5c16529d7714532e638663b62699ae730302358cf7d8026c6dc85b7e03164ae9c16c7996886f65e043820115d8f49fa88d1ae3f5f1db901020cbb233bc8ec322ccb17e66085a959960b796d5423906bccbffffaca7b3ed8f379ebdbca27dedd239718f2fd0e85159eec8a1532aebda70418999851066675857e2ba7f0eea5cb8b3f1d397bc8e90fdfe6d9e25169f5917ac202151dd5f0a56519e73bd84266ed6ed5e779e9356a4b7065c86de97f3889ef2985cf564fd89f519771129bce3a737e301a9023b1062fd0c17a7161e2b0ad69767a903350f7dcc2136a8a6a99c58d1dd087744d3cb2bfcacd9039d59f9946c4b9c3e3d0b53fa7a70dc3c34bd7bcbfffaa8e1754b2b535ad821a6488209ce0b9fd77fa66e2c61a1ca880c1472d8e55465537fededb6592ddd41c3be2766c1ae4aeb7f4ef868c605656ca8a8dcc472a91c6ff0be0027dea463e9635f1659a2027b7d827e63bb49886eddc810dd386c292e93dc4687dfabbfba558dc5109387d408de7a8bf388a4c8f1ea4d2b732b76afdd6bd2c70fc3f6051d49c2bdc62a697ea7dc4e1411339cd5c53fb72cb1f3e11586eb432dc2c1e73fa279d4f62641c48437d2ff6ad0dbc44507c7b1bf210b7747dc0111e438ca902c4746dfe226418d75767b5d7faa0ef26a8bdaf7bbc4f3a313f193cf375c6d10b502f7179713796080c27997e571d55a90b354298cbffb3c8bdbd584b73d9c38c75f68c95133aee34745f201bd0822f1f0f4a37669e375d9ba7437cb786ff292c3280657c8b090ee0c66b06e56744c1a32bdc5b75a1cbd20555130c3478c82779ae46541f5fe286bc1db543a185b6d45dd5c77e4c78d7c5412c04535a4fb94ce640795200d4e50f907beca508480717faad72ac85e123569182262417eaff068927c5392d67bd4e0df5d37a20d7b93425aeeac08302de5db1b9b41d2bf7a1a1ae59e00005b1652be8b87bae88b86eef7b1c9e10c8fc2b80da00d0828a336e861655b2b642640595cf46f5eba9d9dc3848a9f9db68cc25cee49ab762f9cc50c3b12b1e24681504650fdb8a3b749aa0ce690cdfe28f97cdc4429f521844f44befa764801da71d0214b7ce80d4eb9ff84c303893498b9b92b43ae36088b7d5295420fa59489ab37a45724a03ddf5381ae2da17adfa1469a95c109414df09d6d568a34e68f96561fab28987a9b83509e8409017b70c7af8049c8cb86431e7e577d782304670a12ea9dd871901c8d998f1ab5258be4e5cd102c3f15a51662028a1e504647c6f7228e51278d92bf199513fdf5c919c6cbe30fc48be91bfaa0466975437fb00bfce26311c8f930120a24cc93eaf76a95628dfe882e4a11e999adfc3d3d780d61d622182ea47503c14d7f554456674ba85035aaa51524871b06e89510f9296592505d4f96a08f29d26f960139f651150ddde9900cf8ff2cf61c11d4bd688b3d7767bde266dc8a5dc5ca4f6adb3da0e62ca01fa7be87cd12c5e6e46837ad052497ce5d26864a42b1de7b9d2cabe1036282958c6cbce31607e20e0b04010167d6298af9423c9fc70ef2b56afdf67ba7306007f937a150d12ef0e3cc9581b2369f06cf0b1c4f220dc855e023f6ddf16081e074f93fc0398c7f4f78b76082ad1859e2a21ef03931c4429bb6e2a5227fd2869023da5b1453d217679176d871e37afa05612237287ad49c1f7b0b40af0410c9c1ab8dd858a96fdece603c475eb2c401d5f0a67b33ab82a58b9b711b5b366eee5953f330dc807666d736c44d0c00893af486d5e3698997cfe9e3a16da1fb16a2fac017af3bd62dcce831cf9dec5bdc769adadae6ad05ebe0d0444e7c33525e0b193f87fde8c8cb58e36c510e1bd3d3379ad86c3cb0025affc21ca4e25849d71dfa21ade024fbe49c071e13a173d22c257d5c3b5003083530aa3ccba396e63dabab9c1adc6697cb02d2a6cbecfe61d03eecf0c556662ba430eb58dfe09cf6acc78ba8c49a5fccd4fa933d97a1916bd5a0402e872ede853b86123caa992339580aa150739e3e48c872ce69e9554bf63607be7409a26250facab6f5de4a4807c89e5a307dae3942cef1a39be6d4d34248281c646a5bc821a9e46be083b16f76dfd8cf39ee7666abb07d63879893c09c422f3015c58865350ed70e7e1c06486ac61a08cc30a41612522fe825671045e4c12de03aa939449497c404276f646a4bfc806bec0a1d52e16ef49ec5dc4fdbb8b3d981ab1042952c396a009d15e452831df795522262c21846a7be5e910fa132dcbbb5edcdf911f95d416b4d830f8a05051c4e0069c142eaa40c965658e4dad95d2ac85fd5f10d96d889a66dcd766a17fec58563a4da8c4a622fd257c0ffcbcda9cb7879c918739fcad25f4b9737f825e9e080bb57ca396dbb24782d2905f33fce97c5056202fd07102f32ffd4759a033b332efb512ecff782bcf8131b98484d548557d538202e1c3b2d99a123092fa5f9bbd90130a351e47e14dc18bf7cb6b37255386732800b2f214deacb136b1db5081caf9a16b1d1d1a43601b9d36df90b6b23d8817d7b8be2b5513d50ed2355609bde07f271d787bfb57e124385090f2bd99186c7678f7539f5d0fcce47f5771da14a6ef31ac478c14b2d59c843070719af20512cabe448f94b889e7fc0a004dbebef9db5f46f0b867f0e7ae82c981d33285cff7ac079873a33215462affcc5248bb284c1859907443db5da7f031c84f0f097cd9a6aa06d3c521a6c4676e82708771790fa245631d8e65503b1d8ec6b114c788895b674a817e2708aedf9f47d4d62b3175ad2f513b13b0b518768178c81ce0f49408ef017ce941154ef7777ecf9ba44b149d12c92e74352007b8c413414b09581259d7cd203771e0419c2aea2f929fa085cdd896215c646799fdf6ee0eb36db0b2cece7e46b0bd35639ee71661e0768935cd33ebedc93168d82e0c922a03cc7971c98147eae22abb86c6479a20c092f246b830aa8f1394f06150ca00b0e4a3fbd681aaf6452887a8439c44e0097d48d8adfb2c0c3d3edb4b77e52b02d27e0fdf0199ffbf737abe1e4ced28902368887c8da53451f7f67e92721a905c41501dc7d46ac903f30f6b481cde8b94339292b304beee3d544d556a031e3849c2f938dfc0f7e881874b6feb7e02ee2befab0c063cc5da50670cf63ac6bd9a54452ac947c56971e438766403e56fcb0993732c73df95c0c5f85d541baa2509509450dda89fa1273ccdee9fee84ce0286ffedf6c63c4204375aeb98bf38e90f00cb56674c1b8350a0b993fcb9cd25869a6bc1e1e237193f35e39390d24813fbcc7c480c93c44ae0d7675207232ce0d30a2ed3125d4e0361677c66506e83276ecbfcb6738c4f733592c3080c503e45f171037f876de4d7f882612147080935c61e4655b8ae26ec43ed157fa8fc8b34a36342160ff4d8d9e28d28010cb255c4f9699aaae71fb18a52cfbc2b8382fbda52f69e9a7ceda0057ba31920f5ab4845c20699ba94d8b762f026c7fd6c5c390f31f68dcbf3788eb24e61217737c46764654bf03424989e948068022aa20d09154fdfe01ea89fd470a877c7ad43106d1054225f60be5389c8c788766a357254d5a7c6a99ebdc8c4bd95b08e5be6b3e30f0d96fc6082907ec89d4babb32258299062c297a469133bc1d6628d60c59bb5e7f18f619bc355ea80abd181f46af48919bcdf12ff54ef1a71326f0558f646cfd37762524f371802b737a4b81d3a9714cd9e0962735c51b9852a8c97241d485ff8181b2abdd738cc342fb6495ff65fd035e1c5cd134a6cbd15f066f83f88c166258f4f37ce52f50b2fad1f9145f40c7e28c29e81c62ab1d253e34a9bdce50d10ebb79355fd6231190a3c0b8198ba88094f9f95f91998c5a06a3801c26f8660892381af408be2ade7e6d1ceb47524b7f10d3b17cf0a9e1c5bcb1bd9fd7c8bc88ace8a25a78fc62b766a070f21d068b89d5340b7e2c0fff7c42dd928924f293a6a2bec91c00679d647145377004255a18f5ca278592a732bef048388aa296b0c0bd587a9182885f96873ce4ca2a626dd7feee7fdd4eb2d3899614173155db7f83ed8db334be93c14b0903d040123aaae6e3505b98063da570404b48b6bfebc147b81bdc05c2b6198ba3bfee92ab7d862f7bc36c72471e1a2d917bd27260b2d86f878ef3d0c0088cab27ca64ee2c7e059663291705f4e7afcb19e063682c764d2bf9f8fec9531425dd25b042cac346041d5f7a806f6c0dfcd0c87128575e1693533bcdbb82eb6fec3d9efc23b6b83759eebfda3ef46e5e50f4c3bbb32aad22a5d36b2ce0f2e39b0adecd4f99bf24a4f29117c3d0f4c5ebba4168aa8d932b2e28bc0e2b7867c6c4c9a9560b72e0ac192699207b2e5ff485e2244ee4325132cd38a46bf5bf8dc9d4ff00f57cec738ed0d6b62e8ae28d6014c8f8c63c512fa6f9199ad2e115b9a7bebaa19756a8bb311a6c8f7270000ac45aef7c8ec308c7f0c9b2e2ca90457380f7ffd0a566c5abdcce15e6cf71da41bc120932f17f8d6557c4d5b2d3dec4eaa7115347e4a95c482a345b511a4f7ca89cb9847ff91ceae5319efda7847565942575369b39c408aba364b31bd0096e6145986f637c811297c0e8944c0d2255d537d5d1b8257ee75cb61707c35926de618d15066e41f9b21e7a3595883c9eb744db9d1d7722518f8a3e108c79b9535cf4ee540b912efbe1515ecb03318a7b4cb8651f8f7785d1584b021359fd562407857c650e54ac7fa112f971d7a6171e679f227a1dfb7e14dab76eea2d3ca0f4a97b11de90c3463e2e67a4e04275648c08020663ee0cb7099797a79ecd2eab182b0e6c6155843f1d0af09ffcd9bb3f7cc7fcbb5c4631a8fa9c81c7daa342e00b88c684f138f4b0be9ff68fa3cdd55f87f2305337c34af9ce9a27c147ec34eaac89ef028af2ae06d0af42cae5fb5461786cd47d687f8e53cfd97a6a8a5e8f9e22d9347d23dba933f75938d531ab5120246b0e01888a4420f100eb2d6ffcddc113f8f7eea5f76a1b10aa7832bd62b0e134d3c2621363e3e9e00bed5f1ce41aa259cdd73e4934286367aad078f25858474ed441e7aff07944c6da10a8dfb0077d9eed6fc244a6adc0464172d2a6f5f0661e5ea240d888fa6d0af1ed1d4837c632d48ce45b13104eda93b85d7a676f5266f61470f773fa1609c0d46fe77a83db976cf58120c185cb44021b1423463e970bc870f571f6adefbd5d67e9b22a7e1580be22adea10fd4751a5e14b1f3592bab5a7331f1b1a4e7bc2d2d90b0e0a6bf63245242dd5045faab7f55587525be75701d250efd8d3944c7839395152e996592f1948c739c86e7df93c2ffddd4f6265dfdef185c8b79c6e8fcf76bf932b251c4aaa2e184b032112e8a34f0c351f362a5ff033b12eb9cbce981e1f5260ae17acb051e0ae83f59d5e0427726eac5f17451a34c458071c72f1b098562bf6fbb6ee8e127865f84dba38183efea26751e8ab15107d26cde492b0a00ed82891f8df77c7e8e084693a96af5673e6cabe93f6b07a5fc6b17fefd375382850afaf4bad228ea5c60d09adf367a890858093cd365cfbc5e686373a5a0aea3aff2be7a4c7e97434bc87945808775e0563d6bb73c871d19b302bc8971f97bcfdc031259eeec0021b1cd21694bffe8b2687fe61a7d4fa3c94d3bf6692791a28e52ed00deae6694d912cf27e19b1f776188f929f8bd18f9a9da3338a1c8a703f251497adf3659f00fecb787e4caf46ff9a0e0b5be653d784ab85aa967c132f90f6141c1bfb32248730b0292bcc5f9b7d7a6cd2ccc670b2fc046d27d50b4eabb2660f93581dd129230a1e6e532af539d4eeb12a6406f6d7024dfe5aec54fa165c00e39390abd0200dbad82e74a4486338c60a08b568fe3c9f5ca6a01debc1f9aff8cc6686b91536f077bb2800510b02353fdd2ac44126b8580b8ca70afca089172ec766477a6a5cacbb1336ae8d78dba8a4a4debe15da507300d0ebde4315c0205a3c2023d6540514246f5665160d950af79372cb09c2d0b7b034436042919e56b50693615a16a352df8744b3831b24bbda36a7e591e46a25ce31e504536f934804a6db9a628dd4e09113793740ff591806f275be317e9eb60c45d80674c1883f6f8ad67c66047000e12e75f6ec1210d74d45eda5d4eeac6c76c433f68eca4ec40442c4d4d65426d7c4ae2e6ba6435ed6c9bdd4ec854e4fe3649b58486c76f0b706895d0563c9e25e0b5a93d20548e2e018e12759d97948b1e32d29a73f01be67fb0cce613740112ceb52c9487345bb5a709c56fac621446e26a7f777233c18ca196ee36c9d0ef3d10b10cffbcec62f58d5a1f17ac458b116b617fdde063a920684b61311fcec1cd543ef5e482b6c3fc31a821dc462c0f31caec60459bbf5d045a472797a75294f11b1ff245ede75aec73bbd8efc99b19e54a13d2c8f1583c667500fec83115111abed6a2e5604ea0d7d33e2a6e027c5d10b19b69173b2235732d32252cffeb111d8affc3c0bbdaf05b0522e64787b4250cac23b1702ca921ca0fbe2be3c6ec8c70dab4e0091e879fe910aab6e343c666f1658cae0719f9dad23d300899e35669e8d4adbf1b9f78b49babcb3067458d5ff55334e8d40e2e50abfd54f58c63e69d9450c9f4d48a258f4acaacfdd4e59b68f54e0270275f9b25ec0080a05b9bf6ad9cf304a5c24d496f1fe13e1236826e9337707d204b1d8eb656cf0067897dac1af1ac680aeee8bbc15e67c47bfbd23ee73c93737ae2d7be73a77c0a28bf396bdfe83a4437b8a09c712fdaea45320fba3ed2417526bc5a223702f39a9ff66dae3f0e60aa6cbb806990714f09d0d21ad6ecd258b51411b698646efb41296e2e947bac61a6a7ad0058cd8297d268a4dd916bbc94db54b2f2dc5d36194c8ab95d9481e896642c45f3c0e3660dbfc66fba58405842d935265d5366c6c508abae46f559ea85baca25253373b2356e17036fe12d425f0339d4328111da8008de3fad90566ec8c619a1dd14dad648daa31086438763719810d8303bb62c07eb7fe0027ad10b65e1aa5892bfa3c441cb54c5a737da593ce692a88c8db5e6b385e65f5895596f5029f8cc30f96fa144edec87def1201126ecb8c2fea70129a2facb583f6ee1fddb947eae93477dc9d4d357e6443772e4b022a9a6234f0fb90d677a214deee901830846e61bc09e5cce21509829d682c2b5dac7de3ce6639fab220d86a1347303d9478f2700e5e3f78a6b7c02e47ceaabae427306233fe59e5c97906c7ba8691999ef88f74a413d9b33757616dea9471fb3ccf0fe2e887bfd87cbc33c90878321ab29fe67ec8b788ab6dae79f0c6a7bb4fc23d11ea7588cd38e38120554583694564c6d7756b4858ca6b846d1e5532fd93f63bc6fd05ffb6fc878c60ccf020964244172a05f9052cf96155f471348df01abe5d046c6f7cdea468141e1945a438dd6e67ec993a7b2d892002b688a4d892d58d7f01eaa025a75f55c04c6ca178b8e221d0a14ae00313ba613f6716b24eac8792a208be6d099cb511a048bfc6ddd122f0e157f3ae5d46e7c755d5ee52b9200cc3d3273c263aacfafc38dfc8382a6ef023e31fcd734e999afe35583cd121ef86a69667a6aab143d3579b3b1dd207f150fecfc14542d7db2c1f8ecd0efa534c95a74a7761bebf4865faa0da041e6e35441d7fbf6a7c15049b8f8b1b91bbb89490223ae347f10b56c86a417233f1747a195805d3bdce70ca8e7d2ca941dba7980bcd4acef4f8adbd20c09555d1cc8b575f9c194dd397c51241a5608eb4d0a3229f543c0a0592f13144778be7d4065c90f75c667fe6df87546835a060b689a6bd3828b8bee09f4b8b885455131a591e7f8a9e86562dd223fb76b29cdbeb3454da0738b7240a96088277e9b25967e96196d77119878b875511a7eb44f825ded8081bcc23232fb8990d17c080c53e70e54fc7c8a81d0c9d77c043381a838abca10c143a3ddc62a337f0dd542cbc132504affee4e615f98e570d8be9b8a0accc469db6e7b2d21420e2d30b65eb06644b6b99ce9604346f1f73f003bc79f887f9e876e10628958a16d5132ea1693662a1a4aecd933ae209fc098ae3f8fa699442247049192a72ab7d0537422e248fdf8395aab9ee443e92ba29caf06e7f6d1a97e7231601765e9037615807d776e00bbc07c337ce65d8d158c7ac2e5b956e04885d330bfa2fc33a31740abb4c55ddfeceac7c12842b05447974653026d832d7093a0894e61e9b665d600a62c24090b7e6b9197986e98c9e07435a7ddebae731036ed7f8be91d83feedb29db95e6f33d57c22342a473bce8b80266dfce72b187e21958009628ca71ee525fe1b945ecdea0a0f2c2643b441aea9fa72151ae034e8ce15074e0cb7c97b28854249cb192f852e244ee34b4a37b64be0928e0e11a7fb86645abf81fb5b0f8184582796df35d025ee08fffa9c88750eda87dcd8737981d2769e9701b6c9c19ecc27401971c70655242f57a1cd934d31b8f4475130949b5f1354d748ac28e8ad400df8e4331899ee0be0747d284d88ab43e9826e320dbe788ac8cac609b70ac1fa3ca486b669836abe5bdf2cf16f08797d8e957a6071ff90eba7de9ac2a01d7db6641d75b073c367d841d8a7873d97f76ad684745a6d6a37a64fd61548964f3fe3fa610b2067a466ae6a53d6a5bee599bd538f4746a10c279b4bdfa99e08fc7439247eabc89f762314b995c19bbc716ddfc611e3833392dde5a075ed134f0ebad66d029f0b0ed9b1a7642172259620b5ff208691bef18deb2a442cbf53281e88727f6fdea26b5f94701078e8570d24cc400d88f721d619511937025dff7093fbadf14ca8eb9b1f4f7d8cdd5959e26afcd6f928df53ec005aa0411b02bdc69c2866fdd4786bb79d0c6fa2a4afe998ea10d38d30459883a87d3ad75ea1a09f0438e70689030b3078fa0363292145ee0c076eb3bdfa7e994ad942f9c75babcb50da43382e1d9e5cc1b8820ef16b0d703bcd4995cd4643585826e1ba0f79e4eb46f6823d7af2f2050fa7ec0aeaae10df1a69b404e2d90b8ed185a119a4b7bfaa0645aa8f6c7961db9872e5d4f37062e67295ce3108963b7431c451c92fef1219a5a28a5f6b9fe8649b3b9acb32328f638882037db5b5c26a0a53be3c77e0640b9d6d2507743e4b933cc49489d4b8375ba6ac671ce3264775aba41da1d0d1067fc3c5923f8435e9d45b76a3325b4866673c45ce554eb6a073480294a8151a01daa4654946b43aa33d0546a9a1403152047ec4e54e9f27bf21456b15412c7b07fd1450adca9ac7dca3232a0a144fd2e7cf22a83843726ceed1099ac4c2eef78c98cdd980dfda994fe4f01c6555719f1e58f85c7823dc9c375659074dac7ba6b01698a6a4be19169c1b59543c99bf4946ec6b580844393c514d71ea1e7001a465989f2eb43e355a64f4500062f7f7f36692f5530765065dd9577b9bf6efad9748e346b7237deab5e514261ac1732cc58ac1e1657433026265c54b846366cf2f82ae4ce23efc6e1d9c5cb2fd562a966e03492f6fe022dcac0744c074023c192b701a27ed3a4ed4d3041be6bca7d84a02e1d9e7058cdde5f2fba9c583fa0d4fbee680c9cbad7344a7d637063524cd8350f1ce0ce483c4df6eb27eaf206558365810d6ce0ebe2aab63f39227ba431ded9dd999b1f06df385cbd7d4f6c50d2804523d6db376c67b47c3295ce00fc97beb0f519e1fda85fd84a66ec0f7ebe3c9e994fa92c2dbfc5d2aa81a90508f79244f04c821e35a71b272188ff590c0573298d8a4dec28210db22f8d963c3f43b7c9519547a41bc2f8d6cf38ca6a6fe26a455ffa4335b30d0946511122594873500308739c1592e3b9fd51783f186119b2a6bf6d1a78f4f329495ec61eaca64d57afc0bb9f15108a603d3bc9a75bdad2542471eb3ac1fed778ee4e125984be0f5b778ba3b5b0827ae8a7d6b040bd0e4a1ecc18c8781ff365c85c5c17009933327ede7e9d26a85f4da38307efd65e5ff81c16527d51740a283ad467356f0be39109a3942c61468c1b9bbf89d4954cd2ef7fb087bf7b10b770a9add7afc7312529da07942298d1b99a987297b39514914ae1e41c499a6ce88ad99999d550eb18d314fceff8c805709f3c825af05aaeb5bf250eecfc05209c9f657ad5681af5083576df95bf958c9707defc95bb127bf8b49abd5c324e5f2ae2f151c798332020629b8c50905ad219d3e217a67cc1184f72592e63c580df415dabb8a866d547cf24887ad1bd0f86f99f0fc3c1352c103620eb37e03e068bffc6b82b239298635245a7abd9653bab7c9ee6c95b84a091e4e72383a4580efd47ba64dc8f6c7f303eaed2a2a4136a87c798c615f187f2718645d8b9e4f1c3ba92984b7fa884edbb52e30179844fd5c8067b9aa1c499ac1f31c18e71fc5bac98381c93264b8a19ed01106c46c9fb3d75f4f61e1af0a40a627799f8659467888570f1b7d8de987d3033d4dfebe892c28faf4b11eb50e01527672106e4c5a015770d38d0c58f50135d0a396f9a226e1a1d50b3265988430fe4a8b5a1b9d76ebe3db9c6aa65ae65e73074b1f95fe78ac04ed5ceb3a0ddbfef4173ecdcd9317176baa5b30c357822d3075dbcf2939a6c26520878584a9a27e57f08a51e359336c7e322bf849e2de4322667bd04db8eea48d021031d055a570b8d284eeaf934db7a7912f4cdf7c8954ca77c09e56b12f7dd30167f67aeaa0580585abe5710003e29d409451792a7528a10855305b27761233cfd40b6b5b778a447811a61ddbd84d57c3fe6c05970f041a78dd1c5e5b10a825cbccc9379b1b73ec61ad5c4ef5a33c630d935b2cdfa8acf4708ffce14c7806c28692dad23088823cd31fef3667f289e478696b3424a4d668d1236c8fc1f0121c14574c629ff5233c678f28e87bb3aafdf59193682ec7bf1464aa3e96cf0383202b9f5f45462d7c4aa370916019f563e34913a0de3293c19395a99390bedbd930fe6e7602f7222d7ed3e875b4a9709f68c64256a2ee2e9e5bc218b1e7f9a3afbfd236641d458814917a1ee280ce79c5b43adbfbb091648fde37c1b85df603840e9b82326b4554e68ba3ea27fbff6626d0d900115088821c3c43f035e171614b8d7ed124eb6064eacefc6fc3086d9ae6f458ee6a8fb3d8856e369c2a7f98e7e7190d8c3ecdaa6cbd7ce136ed9129a7d963d4a6b03107731f32ccfb5f62967d15e3228056146234ec9d8a61e928c9ad35a27a161f39f2de8f6b846e00e26d4603a66159bb4e6f9ceb9d0ba0ab8cde9214f6d7b046dd6a5f348e5702c1aae52f121a941a56b82c209d5c2e2138c3cc78d281a2cae4f60e0f02cd64d56c421a125212e633c7aafd33bbc0f3c0b28458ea170baf3ddd4a6e9bb0845443cd4e3982f2064b75ee53e24ef33e1089891fb115d53385585b626ee5017587f0b77712d159d9e3f32b011efae8585fa320be40a958fb3d90c7ab158803fa927ca75aaef8ac3594b5ee06e30b0b80c29c8b69b9c9a9ac2c3e7487e36bd253e88a76dee9a3821a209b01b791b5137e59c3e5d5c56fe7c2f900c7e66633524c1ef363ae21f2c4b6e0a7973cca82a2bb0e8f1e8c4ea0607b0c0260adc22030d2423f08e19e10575c59d276a1e486f26d77f560859d6162a0f20ac862754876eff54239eb9abd123ffbb7e7c29e259efd5d5ddb1fffb133ec952d15eb79202f55eb90f55f66c2ad590ee90926725cbea48d32ce73e5bc7ebe5674febdd2756ffda6788b45a3c8467415c63dfaea849c3c53836384ba9ac0afda448a72089b094e18611901747d26721dbcf4c24e59e2a316bb71d883b80855cded2e0326cf5cbec0b629637bf629b670506f68f5ce665f1ac8e7f8ea118739d61d1b3c4abdab3696e0fdd0f8e9c50571abdf92228447bd3f221f884ce568f748876acef6d7b09782f8a243a35b0b12776b53aab20371b5807cfc694635c0c866a82e13ac14f74c22929b36dbd39c226850666ad0b1fa39f8ae6c581783c324d8a5fadfc58a745bcafadecdbcbebacad45b8f2bb9d3d35c9dac22e1ccfb84dc0b424714af7de25d19e07a9504fd67f35b4eb6c76c01257b736740fb96b3848a5ec0df0d5a34253329b69b983c2a9b7fdd1ad1226dc6bbdcce0c6f5fda1d3ab70a746a6aa2e0be7ef22b58e542d3b990c43d21b78fc750d6ff0ae185504d444b2e669f49bd2a12b418b281f22e93d4ab8837373f2efba30a04c433eabfebe19c2c8f53044cbcbe25a77b54649119017d56e259e7e964a532f22c0358f48b6f0199570cb45b61cc184ba82c9bec106bc27f665baeed2abb77dc04afc219a6237c750a79cf97adb7481cc67aaf8b37e325da80e24fd108faadbaddec435a9038ff2dacee9a5f96805d5d40b8d58ef68d5e97472215bd35d5eb8a994ed189c77b44338e6dd972cc7f2431944f067e5bff5d2ec66f0ba20cff899b702fa2a7b3dcef2687f6102309164325cae04a794bf08b516c1d741cbac623c9830e80d1f2c867a7923e80ffb856c45e3f84f31bfbf3a532addbd55d30de27b128561955b464b1e3d220286a537bf5166e24c8aeaef75e1390c93e75e2e28f6231506c66a99d03b4dc64e69ef88a87376be18fbc77bdc9f26e2c08d99838a14c038f957575e6c0e4b521f4da78ab9ed990b3410c06be06e507c3c37814753089673bd9ebd7483557be2d475dc3b9d1450b36ad1cbf7ac845aa6fb3288b9e18e9ebb61174cd962a22af85f8e885becdf9d4c5e976d61368fdd462f6d5be57639ffec0b00a2f896d8c63ea3aa93c47ed04924a2da258a321894a4c3efacbcd3ba0ee688bed6da1b13715539dc1e17184ee9fc4a674f82eff358f3724641693ba2d026c09d07312e522c4bc61186935d2ca54165a24851798977826ea26a176a43f1ac25b4be3d954c08e813a2b38cbe305b88235ac2cc40a17b0948ef025311ff5ddda5c65e15214a7a6a996a6f762928505de9c1d36f34e8aea3ab2c242fec7eed2e7f0ef188ac8b7e5566d7bbeb8f105ac9ed979e40dfb1f84d884be3776ba665b537c9a49fafbf9aeeb4f866cf5f6bc2dfa66060103b6bc399991bd6e60dd5c847e5a58528cb21ec7e869c69b1012e0d0069f3e3d890513ddc7a037d3cbe81af9e645ed8688068c49758a51a2f1bbd9725b77b47f9ceef3e6b9f51cc89878b47799df9f11b18cec2c08fe6c720fd972935bf3dea7dbfe35dd4fa22dce065ae54931a0802161b420a626f5627d49007cb1ad6bf555e1b2d5c31f340b8b3b969da127d8376a4b0a3ca0dec03235fcfe295b0d91a718a89398ce43bd56df5e6ac3d42875e47bb28bb964387b0d4c6c19332784d6c36b10abb7dbb528e7138b82d63c801a5d2fe49431efedc079b925c4455fab408d7c70ac064ff353194a534f903aadb7ada21db0fe3d8421ff361ff482d819ebdf525dea9d2a85ef7d0c324e1c1b53995a223368f5a3c7cd5cc0fedea79a82384bd910846c1b255385b1598f120cc7ca124a9018541b7d66e5d159827c3faa267c788291c920eb0fb99786f14520eb158a4cbe4158505ee59b918035b3a0eaa4c368d0302f124662060ed24aa70c5cbbebb351431f824c85f8f18ddf2b0630d2027949d7c8a62804c54b79004f8f419e3e56f735ca7b7c6ec0363960ca5e86bb855c1e7dd0af08fb4d9259516df539caa0b85ee8ef14fc68bb22cc559b2ef1cc0d0d71321d69e89ade6e7525ad9f8e203eb01fbd425f0056c17da5da99f97d8b8dc0b7968a1f2319bb6fc12995dcef83ec9b7ecc9491d27a77527564795d0d1751fed62184aa7aef0abc8032018912d7311a4d2fc4fce3d556543f04674c3c4d89da608df901dd0a9b396af0f3e50dc4b6b1c563980065c3a1eb53788ed7ddc135c9b1cbc53e193472a7e733b8fe5ec627d3b32d8d3bcee49aa8326ca8fb7e8926c69285d03a162dbebef52c3ae92bd42cf24d6b8e4bb026b0c5b67fd0d574e02ebd1636a96263bdaa087b778088f6574471c249329108f3d0d46ec5a4b033dc595403235f1f310c5a0fb057031a2a0c55323f406b73b1b1aa601c42bf47d3ab5bc2e427df215fd5f671d506335d4e3f721a9e195628b3b73e3d9da5a82e592d40a869aecf474f454e28a2a05bda50536f1162f1fdfbed2c2b0e862664f6ebf6d4af7367fcdef88e3a4f98f86b0be747c2fbcca66ea6e5601bd8950039c2c763f6895c75a2112cde594015dfc8fba60f58ad1d03bd38a4b3fad2732ca3309129018b92b28da519bca02ecbc07f71b5958c23fb1d96ff96ba40687a94dcc070874a383efa71665551a228d049c7e51837364e6ef3f2dd9ab773ce7177da7893e32eaab3e2e56bcc58338272b804a8a2688110da154c53ef60625708f789b128933138fae56e67822e00642d45204fd7d4ec890632caa8b4b4d407d0bd0c03f0aa1df548ca40f45a26bb5eaedc5591d55a4be9a7735a5592e552e87c365bfa4034c6118a6314cc02927b0e1b6360f49dc79b06b9458a13d4458a8cb9f651290e60515b357e1c341495279b48152ba35d09921ed5d56e0544eb20080629faa6107f9eaee03783be36b244f8610569b747cb63e4ee7d7b7260656fa86bd8c7628383247bc5b3d205ba97c19cd479f2f1675e8f1d2104863c9472146a46742bac535496c61549e161050796af902376d9842f63c334f71c6dfe6a54e7a74c378ff34a2c2500b97deccd4015da73eb753e29d9174d58067897c19ce0dc5ed2ade43f6ba76bdbc9bdbe5eda6fe1ffd1357aa18678bd47010c7ea1f2c53306c7d323863ee724f6771c2e03eb3f597497c2a59ba8b1332ba300eae576ba32976a6fc72a568ee45576208452b2777951fddebadff1ffdb3ea8d29468614f373a63228428cc1a91d5df685bcddac100aa44dbccc987dd59290d90c58cc99441d3ff5d93b3b3a3d187e3c9e85ab3df3c66822b37075f7223f65b9b993f5c034e392c9b7e3891f8d870b005e5ccf7a220a564d44418851e52018ee40118cdf5711a1811b7624ab81445437c52f25a3ef60ca78ece8453086429e387c90213deefdc46fbf1c79f2b03e49a560857111d05adb908b30e41fc9381a42378cfec5016c359762f8805b429c4c106e0128270064ac445925bdd90117db0cdeafc95cfe2d723e7a641899f05bbbe9a68b2d5ae8156b9c7129669cdf6cce809f553067a63ace3499db378b23a8c5e4fdfa0e428cfb46e4fbf67420c6c1819d9ca7323ec9f8e9eb7e6a0ba222981b3267697e35014c715beb24bd2e19732361f699cfa76bb5bb182dcc4de2373b4c149fe990dad51813edefbc94e8be5a2cb4b6902fb10faf7f457e6a5f75120010e949b8414340628afda405bff50b77a085a7981ae2244d046899d81b84785eaca3c647cd4e42e934613f922ad9c213af0e748b7a8805de6e24affc30d63977e3d31cb8713d21decaf2cf7bd5f05cb6a9ef5df4ae03845c106eba1b19c98a5b42d7556a5c996f3b1e4b1aa8de5800bc3b65fd171f713b3297e46f2a5ca1bc02c16dcc16ed578704cffe6f9fd6e5d2d4330b86b472eb58f0e4b39f7110b7d3fd4ea34f47054ac20d830a54cdc924fcb6fae0466590b9bdafb7d2914a25b9a8a9fa93b30be1e4e96df890abfaa752fad6f0f3672d357e557f1ea087cb5249a6f5cd6246d9ec5b26295331d8c74d3a34cbacd52ceb7b81763930bd37a38001843a5d334ef4d4109e99f5f5a72ef0e6a2cfd8f9842d9249e532c52f28b9146bdd646cd60f9be526cd1dfbb05f8021e330756f354964b0cb02d90c95041a406f8b058df0b729cbda6a34dc4837b30de8bf0e97ed126d219700b688458813f770159115db72200e88a9868ead290d71f77488c901054069b984fb2b2604ddacc4965184967072add359381594d99e04b19f3bf3bd34383e7b1beba3deadd5989951d5ca28a249f8f994cdb6eee4f57f81a747173ddfdac580d319c84ae8c27b85fd85ad01a3acd5f0dc87727c84df6a11d076b88ec81f6056386b99f8cf13677f7f09f70f677312b786669aaac72798733cc47817e44cf7b8835697936069532b4c16be73e7e45b627978554b8f4dbc18681c455443ca28ddcd0e86270ae5c48289d459ca535b6cb4e29ed28e59ed79d0f959915c23cd09476d2e1249eb35392a7b07d68397da8512d2c0c6062a0e0e3ff78fcbaebbf470ce255a4b7efa1467f1e1c5d96442fc44b3af54029542c02b05ac30e9abc7e87bdfc9db4117c9a99ae18c1ab5f36a23085258a2c162fe36a8027c72f1a5d8f0fae7f90eea8686c2d3f5ad24d6b3a7139b1a4ce1afe2854c66239e73bdf416ea27402a05e80551b6f5fe437b91123064837349f94573471feb09dc6c524fc14a6ef93ce0ced38434f9ae764c11afdecceac760a45008b9eef64dea97bd2476a161464c6cb104c8fa10b61efc66fb012093320397ca9ea79acd6f995f14f21ba77a437ee24889d2c7cf1ae5b9d9cd9cb0c11caf4425a57a2acdd90985cdfe651b072f3fc7563300527b0961b4bd0c8981e06b6a5b1abb53a9fc6563e868e5dfab102f6496ac92b77b435c814c7bc7c7f5e91810be98de520f7a45bd7a0d78b0fb968412c0c9ae5c026117ee46564aa7dfd84996164f4e51d89b1acdc3d93c3fb3f23925dedec9c531cdb2e858ca7ee63db3f4b851f099fcf05d61f957f8e4e028400c22ce1a244c6aa2212b2c2d7d9a3e14f639d3d5ad7c8ef2f2ceb41d9d555a054f129e3b17f0ff83f474289e132a8398dee983d28e559d152af86982d0088ae482a7f2eebc0fdd70c5545d9aad4a5222b119145d64bdd207233c5b539cd4cc77281cc71b09e6a91e114f3b40c5117906b4f2b02d306d179724adec736f198f4344610309e62f748862baf258b6d4ac39fec36fc09ad926441a19f0af82831cc681bfd7ee22ee5ddf78cb87f782d003e7270b3e7b797d7ca9bd56b91a4eb1f3b421dcf1ea4ac95f65932fd6c02a0a37b9b4b6dc761fdf79d0d68987b7b7f88f00c2bacd28b13e41c58dca6ec8f5b352dba8e3eb53098d64aa5523ffec77aab8548567d0625bc2cdc610596f0d33c2e4c0a77b9d18056f47445e7bb8e48326419dcdb38b5d7e03ae4e17dad9851593410ace4aa50bb0f1348244ace26dee0ca26e55271bcdec16410baee9756024e75c0664b21258836c302019c93194c4d1690a9c60a33da7432fa50809096cb77e57666f9d9b1d2c4201509820f82ca7b6686eeaabcf44160b1667a228f26843219a08083e2bf1b3f00792c1fb05716bb750c72501fc41b7203be934185508250a9424fce3a5a44c3f8363608835104b3a38c335878150ffc1d8bb14c87a7d553b59728c16a2e614e8be5689ea668bbf9863eefa61e77a0284f4f1e9430824555f1e8610f6d4592d7b05fbce5ce1a83908513c269ac808d25ba80090c5cbd02ae612176af7a5957d1521fbd4e2bd60d087b5004bc468281b9fbbfec0555d924301919d439865b457b34ec9ce0cf905b3672dac541e6424a79e245dbce40459b7e054d8662319033e8673d9137b1c8e2976155deba1cc563c07ea43c53d2929405607ad8351d5d4bd9184d77f23ed09108b343f89dde6f4b58f37f170ba145cb391e06b05f880d5f8b3a6ad6dba783b23d20190757fd8b24d6f0c4634b974538e1269bed597b79d55392a881af599a828f98ee5a5dbd05b36479642fbf02904cd7664758a7a3dbea95aa22767ba93ec0d43a4c546fc08f287af6d28edac74eb1306537b7dfa30553bf4b552b3b09317cf84f6dbdd0d2b030238bbc973a4b2f4c891985acdd269346d233bcb9d4b663bf607fe0bf30546b2d8f939857aca61d0b0fea14d35dd90dd192c5b417fd7b16a6c9412d0355c0506809b5d285bfddc6d3812313ef10134820cc89b51648dc14e146c3aa4bdce992479db7e4d169c84adfca23584883d0c4e0b9322ae7035e1992ae4b5f06a7b7f172eafc18a5a923976b44b3773e9f78805c8d534063073f4d3aad69d4caa0a8fa89394a604ec947d1914a298378a82ee40a1dac8c553e5113ba285289e20cf0a6dc1b5410229c65c52bd308a79af3f0913d507724f3fc8269b8c8a1786734437a58111ba6601c55edbbe048d989024bbb2b5ee5119009baa89af3384c4096de820eab3fd9af652132f9c61bdd08d79994590e2206acbf6d318e9b4923289fd143c2e9d1a1d27bf46ec85f3764de9ee6c67ea1e26e52602b5fe773b52b4822cafbcb128b5ef6c65f3e5589aa3fd8fed535d6424b1b047fc99c63df2097d3c22c8253407d0e1c0f35be0ad2b8ffa28cf9f617c4ef8e039f4601e1eaa2dc5ed48712ca41b61a93f3f61acde87ab9404fdb082804b0a61e81fc960d139a4677890ef657b9e43f94e9700452001e2c5d36dc26413931a218bf90e5224fa4a15f29f82035d116d621dd14ad8da820a0b1090da1e1de25130a456b216438ba0a2e140eef98ac01061e78e0abe3e3e6b07a023ed51af8f771a65af417e7366cad67bc291da506b41a246158ff7f21de97f0455d20d0c8833f52577aede96ba062e737d12c0444eb2e0bf24cd0a4be77d15512ef3caad7359008a2c9d91cd2da48a8658229fa458601803a42d7376bd4544c6aba21ce5a8824f323304c8b4c8b1b5a83a83e6ea13c3104d1b1bbc2855863090b31088309fe40ba1c76717b1584caf2c5743d305ba2997f717576c469af56ad227b987d438c191052eec6eb90ac481fc90f6afeb9cf84cb74f3fcf58828e78d0be79550301fdcc5ad160bb69d6d9561bdc6b490ca8d6ab2f3cb2d5b625d50cd53c0a9db6ddb7ab4f44fc1067eb2d68c4e084ce0b208e85be7a1f17a02e1a18c092a2988926097440be77c513bb793657017f92b770b90b3072bae886ac5a59e42ebcecefd131c806cb9d6fd458c9d01586f2416a1063d6b99eb5b548b24c9c4ccf266cd5208725b6de8fab39d7974068a5cf7e4098b8cf06e184c7ec003ab48aa3197facacff91301e4b810afdf07188ae3513433faaea566b3901efacc968489458f18ca7ae3d2e02a8ee2f7d50e79ddef97d7f5f46c2f3b8a8fe6715a5946ad9e8eb2b7ea13dff1b8db4e233fe4a7df6ce529b34cfa2e572158b6327ba87beb9446bcf5369beaa7f1f3e71db83c01913122a2abd3cf59d3cae87dc90ee0bd3e29824598d0714bba6f49fb4dcf77a818de632a71242bbd030ca2568de757b2a9fbe59b0102de779146e67979020877c068f7515c4da067a690c85552c8d4f5cb06c00bedae4813680f2168dd1e9be799257b9c68842311a47164d144b26bd6bf19b23e52ff44e48d6a96b3318243d9eaa8c9f0f18f6e1d7cab40fc42e736f57a7dcd75573536c75a4101d404bc62b5499cac6dc07cf0c9e9712965bf19e5effdaad0597d20200543f4ef7a7b7ab3e948b68eb5b2893fef8ddf64a2e03f8284e501e1ee483ffd1b97484f445ed92ec90e6599fcd6d719ac4aac7d3c24e293a5c32f271f077c4130c87e68a081fe76a5175a1c697221e8626f563157a1425047499184617ab9726314662259c5b5807dfe51b4df0d054ab9068f6e3a3c91c760e5a4c17aa00ac54793bba1ea1ffffb0e77c3d591acce0cb5f18a7dc873544ef6636c5ced49a528234ab86d9453e43942a255620cd69eb72aa1658c2354c45914a49ca73657472543f96f9528fdd9c900d95de7411261f0d996928edf2e1c106fd9a8dccd50a845a7d2ffeff75700e81acfb34cce908bf66e2e440cdd8b7801974d901854bdce3e225b8d181bf106cd204f7fc42e0480e4bf6060ab4b34fdb0d1681b610775b5c1ea82a4a59854f47e2d5cf87faee8d3d7e893c2ef225aa609d2298ab0e81e25076185b79979bbbac06c7e1c6a4ea83d8320326d0e3acb498aad9ca70bce1cce5e619744a9a36054ceed5ee675b72a017fdf6ae6e1e63c014204960a4bb135ba3a5796cc8d8197ac633855773e72296a1b4e57c5d69d90f6b1068a6852356176e280f0f1a5ae9c7c4cba6c37749de0581097143f313901ce2f3a253e840524a78bd170a97a634474bd0b7d43d9a1285b15d322c395c6badd2ea33c8cc969b87ed1929c0ee0eedfb6bbedfcd53e928a8153ec8b2dc0849fbc8d572dcd23b2053aa91b583a1622ad246acdfdf677d6224300791831fe10540432649896f33464cd9cb428e276d69ec5416ff890c763c1baad3a7615483adc60cd923e60834c374a313ccbbcefa3d4a935270d8a487f4a01f0b60fc1cec246db0a860614df7ff662388fad31a73effa4f409fd73d0cd47ae63a42c7bc3478d234fe36a7c253e9525cc5c999e522f3b8ccdece789fb8f9175fe74374114e7fb01ac79911f7a823d17c39e4dea77cb3ac2504f2f2b2cd10ed2fe0d9165cd1688dbc0563b4e1c47bff4ce99e515fca8db4675f0a65ae2444a5d1a62e10df4fa7b1ab1b6766d2a6d4aa4ef37c9f48fa5c7c4f0ffa8df9fab978fd4ba1cfa321349ed0fb3001b41be1d1026d52bd5ce41cd4abdba19958b429cd8d41cceafaa3655a18ffe10296e324cb2b6ada4251cf414c6b1aa44f7bce1311ebd43c2611111c90a5b4fb19f60a50ece2a1cd68d319dd36924c43192ba72e836be72f9c26cd2ccdc9330f4878179ebf6afb6052901ab114874f85f46ea7839430b3ef13d69026f8e3c73bf92c2c73e2eb4eacc5d1e55de530f6ce47f46a2106105b418a4c29cf0318e5d8dfaac393484ddade1903742d9c8b516d20d14d3156f04fb1a86a6e803ff0d96af75e119f6866dc3ad5f3c6cdd4f5d609b29355a15ea76db67a75cf0f36c68fc99643341a58286c33ba7bf3dc0f8e3932c7652345f9242cbcfb597d53bf89e90f50f75144765f610a0c44536f72fe290b68c582368e778ba126f156ebbea82414a3e73cd8a5b71236f8545fb6a77383e10485999ac86ad20d19a52de27ed387fcddbcef9bba72af6cb0425e167f94d9ac54ce4e073aa149379373f1145e0c1290d03d5ed28e15e0157072b5e6745b744006556a5784287f927917c1891d65bd477ef524dfc3bd2e2b2385b87c90ac437bb6a7d17b26953fe5c82e111979446d8706b79b42090a90eacac8f1f7e2e6639bbe230c13d15e5966d1e162722a411e53269fc8b0298fee4259be687171c1ee5a96b2c0849740e4f0b4c35b5dea076c25acdf76f99e331faa3b395f163974a626eaccf864c2c48e92d1ad05c2061bd4e8333c70a6c5815380ebc526683b3c354fd7ef99c9f23a0b341a0e5f86beaba004bbc1d3cba08532c0e69ba12cbc84a638a4e8ba91761e1ea16dfadc11cbf63977a592438ba9abd0a648febc4be0057d38449de69175aabb05d18e3c992d723fa64fcc40e824f0667345dc1791dec2d7adb4198c6573995de65096a7e5258689ed7065cf92485b9f12c871b5dc1dfb4e60277bfc7b0c7e18eb0c7a3383c10a323cfa353f7435a1a9520418fb3fbfae9626af5f4207b4a9cafdb0df6de685343c5c1049934950ac842b20d00e8f17cf4a91b089e500a0449fd75982c037dfa2c4fa65c00a93ad84b821f688257fddf676981f6fbf5bfc14ce52145382f47a34b5b35b5ec2b75086fc6e9cb7e44a708d78d5d60758ba802b244a4cddffbc34325122844a2a36093e7ea8a0be495f03e0195d931a6825f894f581173cd3248be148e248ff34e93f1e50c3e3abd915b16d1341a0015bc8a0ada0132e340b24bac8fd92b8da429e40d457436cbf965194c968fe35cb48bd2b8d452014b43ad0f9a0ab78d0cd9b0c2f8ec9ae5d578c3a9483a550e4094cffad5fb3d009d7b20f889b25589943e7028acc14618d4db60c2873638de7957f2623a0edfa257a2b021b33bf9701b531ca7b4702ff135b1b4b7c6c364cb5d7db3916510f5e21e1560390923e2a9730c751f6fbb28758e5991dbd7a47e78a27ae7ae06ec10b6fe8fa97594dcd2978d6cb290f372b0b7b7b48a66dc85617f577f920cbcdfd05936d0fcdd2e2846de59a398bdfd6e7807fd3f4bd0b003cf61875c6f18494caf0e5f48c36129519464074dc69cfffb2dda662f4631863da2b723020609fe00ccd13583e3c4d19ea3c71e77abb967823a13312cc9b737507bfa31a21fb1c547961de0982909b33953976dbc9108c487339997e8d951f348bee96b80219daca16c02d8c56fbdda8c26c558fe5f43b34f7432b565fb53f0b6036826d0afa7d3fc648ddb0d19c08d4bfa7addab9b33b0acba674aef9ebd799c4ff8e5365399bab9be2ca7a449bc5e96fe5a37151563a61ba6047a498fec2974cd63bceed7efefa434f3186708371b4148fc557aa131fec228a2bd181d91836894593d6413fc180f5e65b448614dd1eaaf7562d8ed148137f4bc40f9152b4867afd692c2d8a99a27fd209e940982fb9fbbadcee755aaaecba3ca6d5749f1c940bd02360a2491b36954b0a6152d3903604d0e91fe0de7b2038c654a0825ba8e10568ab054931d83c2df6105aa6e42422d922c4177fa0519e8df59cccc60796bd1e591a69ff457fe4e1aaa5be404820dc34116cba29def82ec9e38043400adc14d3e08913beae2d5138b948610f3f8c212ead17c21d56e8c554937a0d1da65b307ee3280e5ef30817a184841130aed8566a7695fd5c53c2c9e2355037f1e9a65311c0d0bbaba6f8db0a60250ebd5d27b615b40e58a585d8cd3b2d490e9d29a1f21c6d7e0651106f911109138700b59db2facf2cf397bd722a0d3775aaaf8e832a85eeddd7186d8b74f0818dca224b7abb8b5652f14d42589d407ca82be66f8e8095d144b45ac730dbf71655ac203ec9dff79739f8967a64fb17f564f4f329769e92a875d8d27380e6e496c9fdcc052769dfc33622b0703c6b0a34a30d55a35b2b21dc125e8eadec7c6ed15f5a127df3dbb974c171e831c24f3803a71ee9a89b058e043517f59dc231d13d48418c72bc8ee37ae8a54219b439afb9f9e16778d04d2760780b6a0b1d8fee99338c8154df111c05a17b66ff7f7b373c03c67211d612602f083e48fe708dea73dc18d64a40773c3d69e25c9a6f4f6d880b92988db4cfcfbaf08b183d4f764187c944acc9f2449ea9aa1ff4e8096c65a16ec3fc8c889a7d8b6029807222e1a7741c7167da1312078a94f27fd53e621997094a12eaaa6d3afd565c0bc4c94e679ed13398fdc4f502ca5a003f446b0d7a854430b89a8c8eb2fb190582f48e4744b59caab45327ddc126837591f0796b5fbc1dc130bda7bcb887c017e7fe1354564716bfe2660b41ca6413acf0a0bb755e334344dede0af7f53bbead5eb0f02b2afc304bba8f693532a919578714065fcab0e56d1507c788cf32998b1c07c4d282b264c6e961037e886e9bce36ece428903cd8c64a15471b760fb6c47ae9be002b6c406db58d966e3ff581ac8179b355a940a1ae3e5cb696c65d1bdd468f978cdcd9f9895f90978c67a81f9108b18a6b35b823835d5c8a00d2aa19c650baa27d9b2920f70147aefb0d6213df4b90645ca14c903fcbe959d9b5bf50dd0c2827e2639e9100325b281c69f1a15cd55a432703b3657374754cbd3598be2bb97f99951a46935035ac9c0f8f90970e06856296133ea730320bfea2c14b839dedfe878593afa5ea4093846d442c6e21f01b9b614cb2e7a6bbc4ac42dad0e8bb8fae1cba95cea4149945e12b8572682ad8b614639ef5369df9a0fc6a1332fdbcf506efd044c814ba56315c063a7f2fd0a0b78fb233db5f89228bf6ba8fdf182c645152535b68ccb082ca338b3d1d900cb2903c1e3f251fd1b45892ec58b5aa6eaa92a1ea3835722b4d3c7edbd9f80ec0931366ce24e83a60b558cdfd067e292fdb412f3455d7b8c99715d9c01dbfdf95b12319cac5a850dc96d8b32ad9c5a9572c5b32007edc2ac90e4f2bfc141f1e7ec85541ba2104e6419d12b982eabb9951a6800ca4bb9ac7542424182e4c20275f8361f317c822684834d35535c52979e22a28bc18ae25b3f8b8cfbbb6c92866229594177326a17f4bc3b16063c3821e6af23179d82a4e9f1b76e4f3d8af46ca19baf928e27b023f2311205fff913cba1d1136fe634a0cbb1eac4b78b7d9a6bee366072b7958e428d04ce54f3067918e7338f06816b66971285b24f7112f2542a0f5c76e9ce3fd046f16e621a369f55687cb55c0097dfb39a1e89f3c1faba3ed4937720a010a1846f29ab421532532e4144c9d3854efbe47ee14124c0fd1c7c8b1aacdbe2bf67783778c84bf80c1549ede93d9c0aab97884e74c6869c0eda4525c286cf7d53d70d3a7aeadba98073c043b710491894768ba424e774af3f31a30bc4fd4eeb17ff28ed1c666c2354775d484dc317dc51bdbd7561cce11a3a5d436d0d40a9f85177388f70ccd8a70eb42377c7d8246b6635832f2ea7a450d5abf7c77fc4660bcac9cf8e1ead4de8d12c039a45681e06174a8b08a43cbbd5f4d41d1e52816167e290acbed183eb0df661c49b58465a8430ed032a52ca10b43e7b69e1d866a7928b63a4eff37ece2f2c765e6a66187357a7b0bf60411977bb31d72020d1d5ea837b18e8aa6123fd941aee635cb0b8323bbec8f2bc51cfb917d2024bd178879051e6cb4f4adc9e0cfd7e960c58ec92f5da498a722aab1aeddca34c95ae30ca9ecbecdccd2e8b63f20489ec607f1d6506d05aeb046b50ea84ca78eaf9e02126966d43b6e1ea116529e04da68fb38b5b401a27bf73ca0eb0c7001242df00e23ef6562f66db9a00008de23862bcfa8ef3123ed14c8fe8c34cc650a2f3cea7519cf152722d84c337ec0f1a1dd9cb80a3437794e5b7b86d79d727d6f920f4c9446a49d4eb642e07540656d3bfa90c1e87b2dfcdde759c9987eb0b3e911711bdcb1cddf529cfd8fac45cb26e3ae6e109348a06f31bfc487181d747b8607fd6c6ae89acde8eca6307ef400f07921a25873f846046e0ef8729519c899443b011d2cd25fbbd0589db3df84254865e0646b6907e6678769626a11b0e75ba4abb4f7ce564f55cd118d9a9e16f87c866592dae3f67e497bba8beeb55f7072076b91aaf600aa1ce4782dc009aebbfb099f008b273d797c4cb145b05534c0538d8cf94cde1f0779f7dea6d8d3c2cac91844ab7b3df6ac4f95d77ae29289eb71a39b456e2c6f1f08a4af133934f6ae9c8552588da5ba1a3a1ba74439b91c6044342e448031aaa1dfc3c1fd0014ba35d813b92798d2afdca0e287bccee7c2bd8bf705abfc6dd3fa5684cfdd704f6abf5383a5805b041c4d67808e949d9164a381c6a2dac28f8bc630a0ab185bb1214fae801795f0cbf59df47e94029478ac5cdf8f06a747966d6d2cf949158051b04a5fac14288c8a020b43287f9216bc0847b81246f692f44665e1f8b7b0c3a7a387edec38a243d2319108a4f2fb133a0a32f03c8151cc9327dbc1527b08594c3e9942a865c1f16627c8cad1168d66ea3cca72a7f8b64d56aeabd46b3e50e25960c26d668630083ddf824d25ca0f3ab1e95c22abb3f3527fa06d4af954e18fce32466c6fc2763bf2980b9f65e1c91063975b9ffbcfa5eb2962c260b611e3adac9c655cbebd580e3c20481fcfee7fa58278e83bcb97dd5019631829f9bef64406705e1fba38884c5059684a282ae919625e77838a4b653b2eb0ce4ca9a57c5d477a3d37f66585a6603435d9510063f6601f0869ce688704aca034447cd3e5ef1de4999f9996b71e8a3e51c28a982b0ac16c172ba13978e37b86ba013c4883248db3e9d4727b58b85c6072f21ceb1dec5c44d6b6f60711e0388eea9d648cc24dad4aa26286b345e65325a30528ff3a5172017c159d6623736a5c86d126000ec624ffa1c0efe570f1a1c7f00b4746db4c024fd03031e9d16b3ec03afc7c93cd5aa300999be2c9a993c15fbe21730d8a9c43232636cd777ffd36b0180591a0ad2968ad51c3d6607c752ddce279eaae46ae810589eff070b313b0147061e7a6ece326303cb1b58272be5bc7c0eb6a97f24a0a5948cf7073d9588dc7a675b3fedf3f73ce8375259c1144ca1e8f52454e81f7170e8a86157066eb49af8afb9b4b70587489270a4696e7298faa8a59d7ab2d1f307f163d00d12c72c6a80403ac231924af2a0ab037346cd40c8623b9ecc9b93dd5900b158768e9f3ba4ce0d48b5c28b8e8bf59ce5e559951ba8c19ea2bbdaa9be1a568d5986e5692b46cbdc730ff4c287984d59ec1bea91c00bd657ff1929b8486ced05046967abffd762ac376af86b1d76b2ce3fed683bb29fc36ee69275114840c8ba06693f39ac931a0317abbdfecb12c5d2bb75b6f642eee3431518cc46aefedbed874f1b8742beeef73e56d7eaf20bf149b433c4e9fa97b468d2e5b29c93a54ee62e594b02f0d02cb40c6e686ac6ba58727c3a8c56a82369cd3315aa5b2921c9a49f86855776fe35348aa2a71ac0edc97524548ba296e5cb70b98aa73cc17dd8cf1317c64f7c632308c099a42cd4dcf625c144852c005b31351c38b4ab21befeeeb481aa41d7e0cca601a48ea2665680817e5fadfb61812db2e0b8d169f18f06299dff46c118a81a8e8cf7d3b3d58853324983ff3a2962613e35fc6ee3da162b20133a0666242589d96ee206e1baa49f149b029c540461cea24fc09e3205ab15346077edfa4cb67e129c70505d8f5b364a6c3445ff01d3c12afe04788fd4c96c6e3f2dab6f9760f505416f9ec1298a7747bf428c991db08aaf17b24eee44cfe5a7a80f6d58460bcb5d6b4e122060a542df360003c7a1cf3906b1ac8b0fd5c410c2f125b504325622bd8fc9af0be10f746ed0b54d4db7cf785cc6fc0397c97f29e401bca136a682ca63a0b057bfe4b77c181618db83e986e4e3aeecc8bf8115921933eeafc14e3c31620bce9a1c14fd37076bc5968e67a5047dc700d07399d91b3123a205d63b092fa3cb1fc5e38386f2f77c41aacaa2e4c46dcc0dd73a38f2f0005abd4b0a7db8524cc9386f9af9d80c61badd06aaa79b71f10bb9d277d4e24aa9db2d7f4b44dfd240a781749071b19a15ee260add38a72688b70c84b9db92dd255434fec01fae10f2e82fdaa07c9f23f2788031818fd91d98d81568b300491e83eb05f6b7fed1d188afd7f702bfa26236bdfff4eff0b54fce776b349c47270069f0b19c459db9355fb2deb8ac68598b3c0901655d964718404d7233fee9765dfdad1febd677f1c914766edc3b2071b945ed4bab87738bc2444afe588f638eddca54481917e6b5ac1de5bee5f4d57a233e1ab2ef4fe19028077d310f97f327d8df4d9be767360a43a7b83d20bbcf74d687a44835dd4f749f73c20a2cdec08070c28bb735843bb55ec17c5284b6c5b3d82980fa88d3898d91620fb46ef666d9e53676c611b597e8f04cc6090fd50f47a54e1f0e0c38896d8c972cb72717a3e7393e2d71ed2fd3831779cc4d3296b49b73bbdc6a6071758e3501a825eb4f71d4927fac3ce4e98db98d45f57745f2f59d553d612027156a3d796890c6ccb05bb11980a8229759815402d13d4cd8b0e763656bf1bd0a594ac9527d8b1c223ce0e5020dd684fe6d395e5d9918f18f3bf260fd0431696cd8c00453e5e8ac23457948a6a6bed88d897af002e32517f1a916adf33cb2e6d0ec16215cb57cfcad7b3bf2be5a3680e19639ad028e89807882f3632ac89d9adad7c123146882916daac1a8dccc0604888e5ef348e64275480c021f9376d3193012b885c067bb0ba6878ce00900969c23be35b4bb6cb7ca2d8492cc43f85426fe40fdaafc869ee2f73f7d89d95a42c79486ff81fa4887fed22f659cb6a0f79dcda7ec124a7c0b43c44d27e5d3f16b1fa24ad45d26dd3bbec97fb5ca5c8a73b43566a167f7b72ad1ca240489d0eb9d0f1025c9bf10910e6c918c65ee42f0b207c2b960b3d30417555188941f4c580d1b4602c07e2b5468a4abba3180924a94ec102c5cd259f7d96f13752c5f9b42bd04db02273355199a6238b6766f0996539db1e4547b85669fd4689a9316744b18921797fba130acb3963417b3cff860724bcbcc63d1e1c373821978fc4a1454fc8a0760ba5ee3cca7dd83d45eef70d453fa5bd06f805ffae9962bc7a54a1432b9e33eb83aa46663a9e7e9c066cb9d5f8509e931ac95caa4f521cec60f91a37a001544ee97963e426ed193e077984b39d5548faf66f60d9fd3fe6b13954c98553effaf92761acd8b327ea9b71683ce1b6287bf4d75d340cd1a464e4ee6adc3219a08e48cc195d91c48edcaafc838290dbba958a71c0016ef629ee91fdc1d1ce2d0a53b6bbd611ef4ef0199f0fc00e9febe8b2770d754927e4663ef8ae11a9a0bd4db4adb5b00052abcd1bdb70fa85bf2575d5af76b133754b804cbd2313322a5ef8057e9abba105a129d325913f0636286d1e3cd142bd40f41b6cfc1f0126d647b97a95b7c7684d0ea60e5d3f925f618c4bfd121ecba39963da5641f41e8cb45036540b72eb4c553345e8f37e6443dcdabac95750b59800158f0186b825bc31a7807f89ec8f0474427fb53e4f69f816bfda3e353c8881937db8a9a61baca27c631afbbe001ac6b40bf640e2fb734f31b96c4ee9207b79c50a49acf017e9234b0fbfd57185945e1e05a2834a5801bfcb5ab3178febc051803bb293ab2e7ae7dc68134cad5d91e05637b76dffbb80fb54c4b29233192ab493ae330083c652192d8a5cd9e307b06b1429537380c0bf25f4303aea1c60fc03d925cdfd19575167df973662b8eef652bc0ce00d5829778f964e3b348763d8883431ad320920cd60c350fcf5601123d5938c2561e4c398ad6398a78322c6ba3bad21c58df1c38bbb57c133b5fef183b218db2ca497c3640884892d47e6a1fdab9358a7afc0f5289f2be7ebeb1b0234a7b88ed50e158ead61c5217378718947b6c82501945eb41e45ec02b63a6ed85ba463c173d8f0af35f61af28365454c0329a1bac3feb9a6031b9fbb141da1be699ccd1edc829532a63bc5e2f83e175b7240862821b39998067510f962b984c18ccfc108f5a595430f2789a4be039ed15158df2c815964f14789e2cf85f929901deacfedd4cf5950ddab511662cb742d59c773017d025140de3ae882ba630670f4ca5cd248ffe64ee496239787cbf3a0aae0c301078a2f4263d4e7764b5d08dd608d3fd6cdfab01dd18b9511864e3eccf6bad2b8ce39947b9d1c3b5b3f03c7ac4c1efe0adc095a3d3639622e62d051a25adc6a9cad85699dbf225834e23580f16c16f48f823cd10829c33295045da7cac4261a17711e99295912c2f8de163aa18796f60c61200ff48f06ce0289dd92e5b46cbac910dd4cf09c756d62c918422c92574263b03ef4bf6d85d4a6fb84de9ebc5720e0a4837c2b8a9f65ea6d3cb611df1990a83b80c8399f6ec2713dcce0767736bfe93165649ea36c6d5b6bab3d19b3596aa7b8fc3bb7a306bf187f787fd9805b7b1610e4990f639f98641876b74fb4e9558ac01ae1dfa1cd582095e97b642266f27a0cab32271c0929e3e05aec420e87b88f04e4bd56a5a64b63cdb6466eb8b2b444705ed10fcacf743f31ffee3b480f0d062cb628e706b5de66c82cdba7a683250d55aa7f22f2647fe4e71387ea58c6fe0ed92770a1c8e938e916968bdb7ad55f252c6404e4297dbf53ac0e71ee1dd3a79ab8752ec6c89597685d52087a3fd076cb1fe5095b46e4b442dbb7e7b3256b0ba57a4a46adeb60d50adae35f53449655a116018245b0ff0424a8084cf9acd395378f123185d6b9bddbe9a9af3dec2384200bdf83a9ba23073caddda6e8761c6fa132fe26b489d95c818446f07bf5493a0e1a9bab30cf3e68bd02a9a59e5f5e26534007100cfe30911df0ac204a2b4327bde60bb88b0f0c5dd84e66cf3e9f0c87dbdbbba17f5dee9bada6a729bf5fb45af3168fda52d2bdbe0350b9174a8cd857fb8d357407316ad036c0cebf501b6b0f2645853e97b22ce97428e02aa38ae24dbf7084164ad6b95f402aa1b4cd80cfc3d480ada2562eb649c9978ea4d522e0e2143efac65fe77c091d76df1f3ebb17a3315f302e367945ffddb2715bd05d7affa127f4453d17dcdb5e3891148e9893688e0c3bbb9138e59a87c37e85759a00801768302b22643c05c7529008f03e045e87aa6ce1cab2b984c6e99a1efdd953630026773519065c36a8bc1e24246f1558345a613d31838af399df51f75f7f77cede3b5d9ef150c062749e8f6344693e45f739a277d97974a9aab95fda71eb9675cb09578fdb124fb60c5da38ed2b1cfcbdbbd1e18b1d1b29dd9fcdec5a0e07a990aac10690408f3ab210398cacf867e2997d0ba74b9b91383b40c5dc631d777128cc66c557542aeb8771b5fcfa0307c1e75b54d46e79810131266e4523dfa59f9f10ca83dd9884993b5ecaca4af60c6dd8347385a7c6208d4a0693d4aaf809a3a409b129910ebdde85e7e8a0db59efeb6d140cc5e6fcc48497aea6406627369cb83ceb8608db2f43daa425e9855da358526f05cd025cee3e4456157c25050ce6ea69a851a0e724640dc22d9a386a6dc489875c11078d484aab565c1fca78797bc9cb6f174213245128795d6ec0e3e55213b3b176c9d3b050b296c90a61f6fe53b37dcf51d0e926fe8907f5aa64306a7b2e3846f0e3b108e7782a248377c612a7911d9afe198eaacf78a03c7e19a5d37bcf9f0fad69484cd82dae7905c6527cc8240db2d20f3dbb5bd57af8df98bff34963e7810862a500416396c8d7a44281a74e3a8f708a0d115879e0b597d36f020e613e883f840ad4c38e02f0fa81edf65ea60da72db7050363a26a5d3ede1d6e12cad7700e24965ad4a6dce7862171df3475f17a3352c4bbc4e55a1ff541f1fb87e6a72a85bc0622106cf1677d379e11612a0b2d470e934428bf8e6945e9ee75b6f4308ca45f5d7990c38e24e1bf3011e5804896a02d846f07c16b279b939ebb079bb63c5f2088be8ab098f6062e16930b6ef7ca484c3ed51f2e7af0ec88b6c7645dfa92a9458aafad1389499aa9ee82f8baf0198fbab96b57c56e8f19e0b36fe287a927d3ffbac888d4e8bc494d97548dd7036ead6ecbe4a87c83a98f5bc121736728a7b78aa48421c35ae0fa41c28780c15ce1b7154a21cadfd7b4055e425eede7a856681543a1ada3e8c36b1761a0ec6958afb102cd89a82a5a31458023031baeb2b701382c3f0017cf327b40f8cd91f2e8a110e4167cdc42aa7a5df7e55d7a55b8965d2a85e5946e99aa64574a446bfa5e7723d4102098a61ff44cf685c826946f7185839b0828742a1b930d6bc72135edc1a2dec50631318b6cbf5c3830857af02e254462a5d1a6d2ba3200fc4c72649af913f119b40500d3e7aafd509b24669e15fcc93a51d6067e6c65668cd25121823ae6ef3fa0022c98fc10a0dcc61fd88bf5651c046fa6de5f0d4644344f20944a06a19a8fc0df7387c04ed2bbac3cccddd4f725ba089d8c6223786169ddf321dea1a1e4ea3ad61876ff6653a9895291f851435c0dec6dbc6e2ae2fff85ad0622c49b155dbc3fbbb4fb84475f9c62ec6057dfd6135fbdefc19f8af2bd2e6fd0dd8fb9722ee2da81f87be535d15d349ac097f1c99a99c91ef5c7fd88ca5c1e11d6ca1d88f68a8cff9df12e5bd7fbcd732b8b98f9beab753ac7f3195b6372aadad61c5d4472a8216e7f6841d5b5d9d711b3f5abe07d7d0fec29ac014e0e08525ce06ee448eae8d38df560c6002d9bb6f99d0d975c789245d00cffa70b558c6634df52ac66e127ac4d8f70f426d98d436e38013445965dbd00a2b35805222e35d8b8059f7ad88012a23b748a7f60b9825a09a2455e69e68419960fe60760d92bb7c113e7d2fa7ea406d9678399474c654ad168279570f8db08395509843cf2eed8b4ced55b794646aab2eaf3952c01e2fd55b28b3b91bde1bca4c27d1ae6d864e691a5794357396e8d8132bc8628d7374ca7cbcff55c973b2dbac1f0bf6b83f22c8423e3274de80283f27540da68c57e0e48189a1468cf2e2327f40e59f2ccf1d870a4eb753baefec1d096de6a1eed1ebb4414c2dfd66215fea28200c422d577a4d9ecfc38625019eed599b3fa8974dfb7b35dc88f79c37dd4a02565502e87bd9f91dea7164072ad79a47f80c39f47e0b01140ee4d56efe0e09b06e0365061a46e3325954309508c2bb12bb5a7c375f68885a62f2bd505c968e45e9058823e83e1e0a9244585ed20499973ea33237ca2ee1bb6f942e2c2eca8849109579198a128478d1caa6f2d610f342fadd1a256b3df094e4e14910b493603feda84875282583fdcc9f39d5cacfa095ef8f8bba6d5430875422c5630812832922a6d5db5ead6793a0f6bb6c054a282edc4f2a4f482a4becdb2e51ddc718c25cd9b7797e97b9bb51e8d9a3778545dbb2e6fa661c8ea9491d6fabf05bd4fceb5f0119c9b4ebe7db0bb0c45e53b5f9fb867aecd26976ea6c3cf7f64cee3fb7ccb7dee325cd029b0a924fecf8b64cd9102351d051e86c60f2382e1f940dbeb908395cbfe6376eb8ffb322ebcf2713c12055d0e0098c63ac66e00ea4bf3911fd4d440fa451266c3909b06075ae89272c2a040fd28715b314793974cadc68d7afb32479612c2b94dc7d64a26a52f3fd294802cba00d19b93553fbb94dded638829d2fe0bca3401dbc3d027d31541e0ab3bd0bf11ea469026f1a0f8f34062e60832aa831008a48ce3b0bd9433ea445d9ede5ad24d53e61c7ead5eb9ef3dd6daf466ca1788fb11918fd5fdfc490cd0a2ce29f67d239fb441c66b5a693b1743c96a5e99a5d64ac701f7ae8ab9ac046a85f5f04362ce7affd48775ac9282d74a5caecef827349f3f01704539ff77b2d956955bce4fbeeb3acff58e87a800fd79352a532768dc6ed25af0d300de4ffd681b4c01098dd125a25911378aa2d6881979f7b753046c64625135f98d163518bb0ce1b19522a64cb85de1ebb727af983e46c8f42c398956cf2d06a47d32c658ac902182f7c61903002a2fa3911ead588cf6b95322219d6bc06d2e5dfe6b03492878444521f93fd4dd25f534c2fa726c70e5097ecaf746ec2e8b386d10cdf14c5f936ed426fdd12abbda1dbbde10727baedfc4c525855b110c93539bdfa167cc9244724da380b8cc6b8d75931aae77a1b50c625ef237f8d76c03c2cd06a9f44bdab63de1336ba84e00cf9299047d0878ed5505149f67ca82e3dacab23b84ca4c92b141a83e47099d475ede214023304414c80833fb8ca371f74b9beb2f31fd72c40ccdf8b1cb1586d32412d7dc93c9a302603e0e2e069cc899fd9ddbf887bbb7d89d828f3fd0f78b9865cc33ba3c39a8ed65b6376f2f2177646f29eeca99da672dc7f2dd32bb0466b2eb2bbcfd2af3ee23efce0e904c3ac29f214c36868c4bee3b6cfe7bfa583c3295c85f97f16a642dbfb57019ea109b0c5e3760c141eb47ededa411f05b842098dc3e4d5bb1ac99db04dfde12e13c987c07749399c8153f7b2bf9d01e10ac36613cf7a673ccea243d8d7009ba3abe31d7220cdc8712a00351e3c42327ed6fe1bb7b754af7a4b1efd305d1fd4510308fa68e4cc753207765058c80fea642d7fef20abec16358063780538bafde8ba3481c8092dae67d413d8dfaa5d0535275154937a62a0dcaa3909d2544373c089108391651b1c8f5f9233619f3844f4e991a8efc5050890548056ba555de5b005c3a744e63bb09f8adaeeff4909153b2f3ce89c34d65e23889b4b594a80b5135e1e804cae48be33f82455f76eb9eee95367feee85cf43b4aff1771bd866a910f12b2055de1dd087f28c14e22b8346ee397b21604ba7cb3899c7a276a3d309304300bd0a8da88cdc0de229aa8c629352ae28c5afa6475111cf24168d4ba1485bead96ab8f54a86eab0fcac1d240e86c01fe01b8d22dcb4c59a47542cf6217883e40107a60ee3987e52e226b03c5f2cb0574b656c4c38407217b15f50cdb8bd91e7d790fa87d2e80d3af872c293ff1e951954fbba8c9d2adba5137bf07e95dd658237f37637ff44057ceb0024921efaa094962e278e6918ee2e67768c71b3d9e974aac574dc224349525fa9db5ac89e66ed405ea48cae3518aaefbbad9d297ac8d79b4f3f3b6d9e4adb950076dfda66998023552c5767c84f45ee9377c5ed4d75d292702d221c88554ad251e68c6203813b319f33a92ef5e053a10ef1526967f1436ffcc0941677ec596b16898ee73258e3d948c506d5fbe30c292687b8abce220cc55f560f23a303483d3b6382de73f8c3e925b6fd04cd5ca3ad371eb10195b3ac3f6137334e037197d861f19c1f7ffc04f9e5887077bfa995f09026c88f4d74028392a6ae4bee995061c7898a93b58cbfcd12b581d1a66b2638aa64c6232bbb4ff2d79ed308e621f9ad3295105e612e6f7c84ec66b2dfb5e90586ea893dbabf872eb91f0f990d55ea17b4a34518d2526d8ca4a16b8e062731e6b5b4fad0fd74929f10ebfa9e5a98f902ab6a452a69a7aa971dc3175afbb51746afc8ef64bdf76074f2502e362fa81ae3c9c8b4c967d92ba20a100bc810e165e40392b2efb36e9775a76eccd507517b039d27693a4c9d3d4e25dfe5a6d0a9369f0fdadeea7e93d6493dcfe85b724194b5f07c449647d5ce4eb620cabafb745d250807d97b426dbe87f81650ca2dbfdb4ea620311ae59210c5e3cfeb3422aba6224cf60cf859998f2b8bece3456fa6f006678a7ae54c5ba7bdf84e4798925394e9bcb1131bb1b4c0eeb309e18100439e02d5e492194a40acfc0a701a74a127ca2c500d08f96f64f75ea9fb1b84ca916c62a81f4d4c90c6958f0eae31436f44d303f078a4806e301682e988ab3d542e0e1f53c4db0a90e71b2f721871e16effa35082098c0646c7999b0a36c71042ba402a0990776648b63e97c15736cf4eb431f23d628962edd115ab791968a526cce9aae5c21489953adf8d49478d8980f14854818c39356d3396a0f6e2d13c3844aac0fd27a7028aaba1d8d65057dbd4fcc00d56bc6c554bdb46c7b84095e53032d091c3c6d261b4d13d31e1d1c1d9ee9405ec5f1ba9950bc72bf43962923408e0bf7e01bcf4c5cafb673169f919673e2d616fb32b21ed98ed9863f53fb3ec69a5ae011bac9f450e0f32342ed0a5e871ea9009ece703ef933506f9996433e404d70c676597f8cdc3eff5319913b36e1e2c39eb6f91211098cbc0d598e16e2cd697a4140da406ea12b35713381a06af272e6074dbaf7518189d6415bf45cbd229cd887805eb5dcdc6f40b0d51786aed17f0abb5503fafccf45adb1e7ade83682d555eb42a9f093245073f732945145364b00c60f9cce040b3d2c5fd5b6fe3e4036e65821ac612d2eb84df63d9a1e444951b30a965a12474728cb91294ebc6648d4e8c4510e04795025524524e09a7a71722d9d41f5dcee1121d043c9cc4fc5c0fa512c6d1b03dfc59ad78a044fea0c18a106ceed1a9adf46d136fa7af04a600c227d7bb080328b59c0d08bc0df0591c73d2c5e7ed2991371dcaa1cd5a0710cfd0a2d4e327047c0a698285356a4f374c2bdac379b8d23e08e59e61b24d91690e337cffdbdb678be76183090d706d9de7decaab01aa30022e18b7d0584a7812281320fe9c59ce76058751b0f8d48ee7f15411ffa14f3f92dc927d457545473275a9a076f193729848bdefcfabe163c7fc4524d155cc9a697d2a54e50aa715178bc2992bc33d09339983458ac17313c3d4f0f7d1bfe4ca67c7e69a4718aba97ca509cffe170e6208137d7ac4246ee2b19a3393bb3768c401dd527e0d995b0c81235f489d615a20d1b6b50eb7062d15f2a93670e2e68dbe6930353d9dbddb3315ebb59a4d069babc314ab42f423b9aad5600ee3644c54066174f8ffb9215c22216d8969cd2274f1ef89a4c8a2b36fbfe758089b4d82f46eca49f5260397eaf780beed407a0e8550bf0afd576d7f1e8121f40e854c2e67146c36269ced3a11c384fa62872e570a379a09047388bece4567fd544298d5badb9ff8b1d703e564c5b71d8f2cbfd5a248e4204712cd382acfe76b790b64fa8433dd9c4effee8cb398b92fbbd9871e7a1f075833c252bc7c8724f47aa66d68f3d7679d71bed80aad66073d7bcef24d4f93e26c945621b4feeb4b4d485b878c06f28a19768584d41c4bb49c382278acfb3aec8db0c0f31d7bcc11ed60df9e85fdc20694fbd54b812fc93ec5db79699824bf3212fb19792109a1a061ec39e5850bb3fc4b5763c6d32b4b73ca3fdce2d5cf0dd6f74b28d055be9561676b188f8be72c987c065c6b9e524467804c60efe757c8da1a5811c2d9976ce3db16f702a8912bd32db2281a548ce778016a7ae3f58df4402c43e158eae2f9de7106e15b470161825d013114f54ae62a01df87568410a75b9d5e41c6e8c86129c6d259e71377db8f9b9bd5f2658c3b22d21197a8200e13715a2aeb211f987cc4dc0b01eae83ca6751993e2f8235fa34723758ace4645bf6a624aabd040586bcd2ba3078fe8780d4d486cf516dabb583fd2349d85396477a5ef3845963c05abbfcbb9ca1995065d888f78f7aa606672d971ff70fac61e1d19c09716170ab5e6fbae92a810392c63cd8f5a8b386a39221f9dff1da7a6fdf59b5829879d158dd32987c9fb27bf25cc1fba54415eaab048960956394c1d5bbd937bc27e786fcfa4e9c2bb6a77f0e810ea8c94dbdc9eeea231e32faeadda3e8a4bad53c14462f148525fa527e2b60c56cd7e1110a3dae0bc7e860279f104c44d9cf7e3a35f26513b506ce33139c4c3f9f6afce1e61dc4992b95696981a352cfe55863bead368b3f7ee61f104e5510d52ebe355b7ad45de43dcae64958e420868b6fe6496e841d4b5f8fd493f6a990a4135ee066c9cf03cb293969798bfd7c94703006c0ccfb7e327c7c6e49849fc46140d3ca117ee1b9df2c0ea4eff2597a18e580fcb7e755c7840bf308cf4b78a639643c448997e14fda9f9aa9b76d45163e1348387254fb1164b34b9bc7fa819d464f407c599f75e9222d3c86a6822dbf4859a71a5fecf232046bed3b8965a09fa4ff4dde132b4fc092f3f301f354c7180ad8a4560511c46f8098e74cfd373edc0cc03d3a8ac1c97d5816fc9c930ffe558b5667b1e021b0a1d0bd9883779bff64b57793a9937cb2e55b05ee51c6c911d8282b1a07ea02cc43e9760ac9e558f71b9f03721cdcb5cdffe783ac74f0e0950a85ddbf937f140dd9bcb7fda72aa087dadfb8a9f93c466620e4dbd81e464d879dfa5ab3a043d5dedc5136941369018e128dec6eff109df7b444cbb94fd769a1f266aafcb6b324ddd8d4df925e5d6b9555f8cc44ec900da602d6c70b4aac5dad4e8f6f7cc591ddd2fce5fcac4447e55f8b4c467e728f8d91dc35b32f108ebaf5fb742d34bf24cbb7852b1c8420eafa8e44ab5b50fdb84218021680627bad1101a3fc874b142a0ee00c7f35b49284719b7f73cfd0751485e2963059571e0c340ca5385ea155828de05f5cf67967aa0a951829ab30817653bde18dac0bef67e66b58a9d2f7a14980c0995a42f50429ef706bf882c833f9239e4e28a027c68b57277bd72cfc00b62eff4053b6fa91c14aff07840bff6a063280333d875bd75ae02ceff0710229eeeb3b8573a3b77016521c62ed516f92e8e7b04a9248bf1203f8f06b524623d26e3ec4ba1d76ab5b3992da973c9c9d2f7dbf64c23223b3f6ce651231669479b232024141dd8c0a8189293d1f8de81af0a057ad2c9182ec42530d89d61345a5509de689ca10bb884c793b9f67f84c3ca3c5fcdadf0df46bc7919ecc33e8de73c75243f66c34e6de51b2080219d2a1b0390df63b338d0c71a8571a3fa6862867adc55de4d8e21d30fbd0c5a48854b417950672f6e28d87b9a8d61b52766af3719a128baad3f40fa38ba6ab04cf9e1fe197c093f7bfcd05ea33e7a0b4f69a4da73b552fe299882b30f7ae3a9588801c183af0b31a161be2b30a0f361390ef853fb2e2339e3c82a59ca75659ff196a54cf006810bb206edb413cc3ee2e3f2e5e3b05351296e01c17e35d5d8de4642a1785d8b9d08c00f62cdc681cb348843422e8eb8495efb8803568ce37078c8bbc13c3fb2c6d9b3600d7d085d101eb386b054ce4601962fd40a784cc05e38df7725d6f95c1cd125ff702448861d588d27876d5b621278d3ed789890b843496c5ea2717c47347e988900b5da9fe6919374fa11a592abb865ef4c395596a6cf0d13c68959627e16b835af15b73173914a25c02bd2cb8fd2acb8cdd82f407c3017136b879d1d1fc507f2cd2d9c8b0fca720549c967932dc5d9fe421b927f068f2f11789413027508732e0e1f254b82fb8d9f74d3b3021de12064ff31d5db1a13ae244ebcc8bcd668cd704ac89b545a48e9c9f3013a91e196fcb50cc0dd18e90c02e9097cc83dd30aa295870f03400f091e6c18abe4152232bd1a540f6a17f81f5ac937f29b51da1c605644cfac3e4494cb065e18bda1e13994247e734719cb5e514cb999f1f9a147c83fee48b0a258c8cc17a0135ef4eca13c9d62c6efb325d6b1bda6e71d6dae07095ec721233d6c577b753516dcd2c227b81d1405838dc5fa62a74f5bfdd444943b5bd68e48cfb7201227a58ae076e6bc74e19a611c357062be9ae9ed7c5fa03327d2414a818c780e03c58861182da4f772d759ee035e3e9cc37e858c25b566a5005cc28193a413820cb3c724ad60ac46799063814ab6593ae1942e12ebe759b077a6af4c50e245602b0c05f03358b43a495e9d068ea55a29c173943c44f751ebd82bcb820ccee5f64bf07e518bfc87f351436d4e36da09b174a722c29d86fe8d1b989706611cef61fdbfbd8520c2a40310e5bb91810ac03b89438381c1222cee4c0af996081011762cdcf28ab2089861d888aa806327c8859ff5d25cbe73076982728ae9886982fb860ff8422760a7f4e89401e1ccbc644f91036278c245a3346ad462ab46523319afa4cae6d46b2c3230458b29aaa1085e0d18b20cbf61339358d80131f25505479b77decb65cf3c0f896655eb519e3d067b0547d9b78af4fdbd1bd057d3439f0b9b836cbf65495aca853ad6a09b1cc605af33cf157ca17f53238dd4cc261d0a089f228d52f423c2ba0188c61eea648a5262c60042ab75d99bbed44a499396906da313a29e65698f4bff0680ebbb327290f8bed5e0bd52bc28f6c11ad22d9b485132cd9c48bb6c5129830582ad6c19532ecf4d9ad8ae44b8e57404ddbd7bf6642ad1b4dac91f238cc8d55ce9b6922e1b4dd3ef9d7fb4b9a14b853493fd6b0f8865e360d45b8c5d4368f07b8253291c9e457e08ed26b891533171e96e307888473534855b09980e42de00c17554e0ffe6b6b9c46040ff93633a56e1067432c8eeeba430fca229fbfb72e31a921a079ffc6c939a6a2ef31862249c2fc8630877e781cec5eac0f69026febf9daa992ae7d54e853f08744e9117f4a6528d61ca7ad28de63c2be4505a1a355e8b8e0e3843e10b8401fcfbfd3f565fe60dfce5b6ca95e27a13380cdc55a24013dbff24c57563d4fcd1d0bff24a97ab9d44db96d1a89bbaacd368461aedf7c16a2f99b818fbd74fceffbbca7dffad4f4ea045eb6af3fc91935c2200bf5ea7b39b8ffc7da3a3d620d02602bfc3648d97be37a75d9d802c2190b99a15e14b10226bd54146352173568759f720e5a8f9f90ea26ea5306e4301b13115779bb39d76cbcc5900682f5ce64f3102e61a13e4be0395e6aa642f3720dd9be0ec6d9089daa672b6d6d1f11b5dc6519306e95ae0403cf2fd082d3c78e78ab25e4d50fad6b4595055c79a3992d65d9b33b05d17f7ba8f60542911b8d58bcae5296b8f32ddb594b609d8692d2b288a8f2c28ef8c479ea6abb4e68e82f76910f8cd6b3e48b0fe676318511c619015ec4e0216e58e297c5b6e2ea2d79b4c48cc5eb5cdc13cfa5c8bc5948c89aa5df240ea78d706dbe48f5d7e13e659a2ff982809c561568fe5b5f3acb4e8154c403585bae366431b37fee4f040b555d8d4db72ed578ab4be0be7471589491a5b7c0c3bbce5a5c3e0699b99079207c7032ce2eab510ddb9bd8bb072ec693891267edeaec078dc1f556d362c2c8d10cb46119cb67865d84ea772c9dc2f6c01e262b0cc518a996131163afae46f810dcb82532a434399d50006a92355da102080bab0f123d2962081d8f9ac30091fe8c46a76f2fcb90de9533decdf1eec508430f071a9c5e94a4e0532dc6a2f3dec02925328eaa37d563b0d1acbcd8e8f7317d3cd243182cd7af93f04bc2fdd8f7b39944da54c1d0e72e462f3fd4c4d583e641716885871f999376f2467d574bf53512c95781c78c8bb17301f9d2506547e8fa97a29343135cc536dcf118eae0d88a68fac5e509703b00c48a1a0fe70c4cb5d85bb322e805cf969b02e21a08659767801f8f8ac8eae3d3569eb07e902f2c6d9512f1ad7b5de17c59abf3624ee3f07b89998e0f94a936d6fcd08c36baf0ece95efe378c8ee86b62e2c2a63335dc933cc9defc42013e35d3cb60f35a47bbc93e67452434aea03071959adb985c50a5e6547ec8e328eaf91ef7f9d08deffd3b66e845411f5e341ae88b90648019d9349b4ffe410f12f3033fec2944939ec39c97a9b9412db9879fc236f8ddde34bcdf0f74556b93ea4047efdf6970bf64ea66f1df571b49aae74542ad48a46d408648eb54e562f4ea277f6d035bb59c7d05c30e38d27de87352359faa9eec3b08a2bb085f9df98c8faca134b91871e1e2685f9ca5c80c6cdd571d20a2b56a9e9abde13cff6e80d16cf23aec0fdd34c267ad344bd48acb947b1a17d8f3c55ec2b98ceef1cd179891cec7b271b8c95066da87e66dd743a62d7e8298710aff7987aaa115fbfbaa5d1f73823e2665f5d2072351deb3226726520f5f9d0b719b0ac50d896be1d8b52b105f2f8f0be738c99efc6785abcfb82a3cb756a31e2b414edfe9b939cbab36bacbb227ac10f87045125599d58b2aff898ac85942a82951fd8fe8f4f5362e5f559abe9e4f63f7ac371930a2471d806f2b4f7801a88f7073484b1c90e4740004fa5800f73895f24bb4f6eae609373e70bd6531150cfe737e7f35385c21ecc04587d666b5394e70d3126bf6838d9a55db73319c9f68aad205d328a147a0ecd6045ea98494a91f230ea339a0cf10ea597d4df1b7752581b1e0fb7e3d9a3797bea393a424909127763e176df85ba1379a5f0daa6bcf9375d178be28f67c8aa339a8b2495339efc842914dd5a53ff0a4f11f9f06cb2a1f1e086be00cc225d10e680f8212477d9c2ed8558118aa2f882017c68fd92ac28019fa8dfaefb2a963a8cb4589fd57e1692316545ce2b03c6e29321be8263161481f333b201a541854e7d73977da41ab7a52fe04aa51385715b20e3c36db26d4aad90f3abcb3cbfbb96536168cd4336c80516d8c941ec1eefd55ea03ddf9101d8aa48a83d43a9d4a5918990d1734b3bdb16d10c04abc14bf4746bcec7f4ca0f584cce1906ec4187b206f7ddd123652626ffe71491331b19e94513246a0c14ff02a631544d7fbe41fc454e220e8d2552fdeabfbfb91d0df856eec871f16eea8c3aebe9586253ca51978c835ea97407df47e85cd835e29889b47ffa3272b3c4b43a7179968a8015c3a586a45dff054e4f2a8e53499adb70995a8a597e24370bae9ea496f70feaa073f3c469554b46d44ce3b623288936adc46fdedb75c6e0aa5b326bb6cc5fdc3d4d8e1096b642e8703155286fd4ea834f8f7f8467ab65f7deb373ff401ce2860053f353f0a447dc03df0e672400b6bef842a8f124dc9be23352dfdf1da5b0e481c599f266f2c49b69a50fff0c0aea4c1e96ec3ed6c7e18fb006f97a0a54dea1ae6894327beebcb38dc3459b25ae813172e29d3e00751e97b067cf046097467ec4b032a5b06c99f3dd907651126d813a33d36ac8e0d34f90787ede34cdbdd08e0b1176fb00839f1a1164cc4b59361642500042064b60f9a12a743c4b28cdd824fdd83c80a2b99d577e26ab09b9ddc303b8e7e296bc894600c37afc28ab9cbb25519b116816db14eca807dd2d598a28bb3395f7bd8f55e8381374df463ea44befd83d5b5d267a97cbc5f19ef18fb7021dc88129523fa1f525ab7fe83b2e004df7b52043272ef35cc66dd095dfa07e91b98ef685e2e95d8fe58cb7c9cd98da31a1945ec6b65fd81ed4438667bfcd907da76112b575bde3a04b643380be7fe0d976e6be2ccdd0eb5789fc50b57c46a370f2bac2d86cc22b481bae13db3595ba05a5118243e51b0dd958839bd3138fba69c24c852e04acc326948406552170a71a3dce052a134ac4d4bf3e60b8defd301e0e1da816d95abba31f92c71a7fef949ae192b3fcc061d642c53be3aa2a37f923e884ceb96205ba108e757444b5aca3ff90bae02dac564920d3d1cb52128cb2e2b7ff5e3860aca8143d598ea84ae6308b7e40155d9ed51fe91baa9e161507bd9b476db2966824dbb6f48e02ab2833d9a18391a1ee6b348b8883cd9bb71876912501aa5004ff353a9b79fdd60a63cfc16960b94875788be6cb8261a53f3283223a87648c445d0ab05aca590ad9db08ac9d65cee0c428dbac8efbd98f10ade01d459bcd07010465c1b8c568b3fb74a3b06d59a51eebc28527c2ada12ec258c216e1d26dac6938f2fbded8be8e3e342df6bfda2fda732dea4abec237c822bd5683ad90fd35eeadb849d0c6eeb53ceda4742c3308b148216cfa4ac2ca2d65575c5f7582b79fe2a5c049334961094997408160f4bb8bc4347b9d72d4624af9a6300637804ea0f6b8b06e4cc332eb3604bac4fde03a5d65993122f52743955b7b3299ad0b38f077c7f729ba090432589a9cbf6174f2caecff7024572cd0fcee43058f38626c29b89e7b7e99d916dcf2b00429fa4958784b8f695cdbe6ab03358809f41187f481d2bf03973019adcaa87a501b0d3304a3288880d51d9730422227f279c496753f3e68bf5639f93079d8e840ed8443cbcea05786cf7d805bb2f31f1cbcd8c1fa5a606e6dfbd9a7dd28573f719937ee1316b1bed13df9b516bbb26bd4d838d599d23b5c9829834d8f12cd608f78b37dbe3ad66b3755d2692ae43c3ddd50d7f950816245c0af6afbb812d2b438f85c4ea448ab6148078eeafbcbc71af19733963b7810377de071edbe8e34ad38fa839d073fa3c80786b4b447af4f5e4e3c1e1fc391cac9af8f5ec4f2a2eb44ca0c33a01c9c5e8b01a6c34b6b1da3ff55cba0babb0cac66e8e24c3126b3deb66cb9eccbd323e5dbaadfb51351dfc7ff80ddd45fea119ea49b9dd3c97471add54042b50caf1bfca24244085b8f8f10c49c5bca0123f549996de947eccb288cfc6666e2a6b8fdd3b30b0157b0d3baa0df4635c4043b7a0a1df243b3c6f760167ed1c9441f1aee09d1f91148d7da4661d11c48757fbc0d408a8a8a1a8b111e5e8bcc132e7bfb70164a11598a117c326242c6a185cf240ffe7f8918503862befa250a93991e9cd651fed3d8ca9cb4ee2fcf7c417fa9707e158831a9d120f8adb874e6dca39a1ed8db7192347d1675d3d6f3f118302d26e248a25fd6c73b917b438d2ba1029c515faff5799ca63012e52d5bef9ab2e905d14118c55fae05e9affd5b93e18929095eb61a4532aa114528e0b37b1d7f4312bd6b956a1b65bcc1616aa0c53009f9fcce9ffb2772de45c2899a976a0137429c0250c6cf3c192678bd173298c341ee1890dbb898215c56bf848212e8dd1f445743e405774c34450fa6c59ce0618e112bcc252182e6c6d3bcc04d2c0688797e391d315cbbbbe5e3f0c3233b6fbf1b4aafe1c28e0a97fc923496e41ce6baa701091155401c65436d5e666e3b918afdad811548da381f2cee2d7bf6c036c200efc4bbe8eb29e19d3c354d1c6bc8d2638a9a2cf76c64b2816f7808f8beb74760ad261384834870d49a7d99c400fd9822d9aed6b225870ce01657acfbfcb1ccb539939de3d3cc963d6c24b430af56e1022802ace157da3ea76b0551dbb7c5b506ea1c07d981d79ef88c084b8b31ff83d469dc32ba52d5da64bc18b7653f0ce0bf6352bf1bb896bfe0051a2c6e2658f4136178bc3117c394bf330c7e06957679cfcbebe700a5209337dda46c841d9269cb4e340ea8ef8a3401997fa9d05510f416dcc1d7eb624081384b299d415ea96cccfbc623dfc12e2290345d42b311b04a55ecedeceb0b1ea4e0aca2cd5cd4481d41b2d2bf82235d4c9b1a12a48be04e953e01626c54f603a315851b12af4d7413a5a0491c62aa62d1fa5c24907307bb6fbf4b4e177fc37231431b689644af716ef0be87353ddc408e3a3843efef9120a4cf63bd10c0a34beb4015af96291b4081920b6bb2e505ad1bb2fabe932d2783eb8352234d335127fa5a3fe58dfecdcd999ec5fba4663482b534e72b34c1b7b8b4fac1ec475bd1d217ac68e0c8f3d5ba79a7091ad9246d00fd964460823e0687331029edd1cb0d5dc3e6a220e0c360a1ed2ae91024001935b272fbd2ef5ef82d13ad3dfe8e74e986ef79b18d77355977d18e209f8f91044cf30a9a717f1ed537c113640cc99e5eaab6d233969439483109ca7e509d24c70c854396eca57e5d0b1ad4321a3fead2aa0dbe155efe0d3ad3ba92ea7b1bf8460cdb4f2b507dc6a08d278279c27526476a03ecd47cef6bcddb48d4cb21776f39abdd54efa5ec93ad0156ee0ab2d933717db7b7ef4cae1996fc19a0587ed0eddbb0d5de30390773f2ecc150d4350ef2592a77d2d7b3bfc62d99d71210f9583f7fa0ac6dbd35ae532d5d948e2e90a96f6f68c92db6db584a65977467d4da26269987b4c8d64b7a317d9813b342cf1e63ff59694639c3d864c244f535e1f911dfe5a7e66907931213d83ac1f9f6c3ae474f6c135398344a4188b9e0883c55f04e842210a04b1bb9b1eccc8938a7135bc230e7403671f69742d6d99da56c89eeadd39e7bc92b7ea80bef4fb41136beaefe7ad19fd06d7a38a6b1e450d21a97738a279dd02a72d6a71fb14af8d7a3ae7557c7b97f3433b9f21dc3fd97361b5f43c27170ccf2555bd2908afffbfb5699668008dc58645f3fbcc7f4860f8b9e3edbb21d3802672c1c0cbce2d47ccf9ca690d5b57c4987968772a3bc550e4ec5e1c41404e29d630caab87740979de8178a4ee1740a15810b3d8aa97ce3c2b50725662b93474298adbae31a1b24d1a960bce10e6cd18059137fdfdeca845512543a7486dad8bb0bf77fa20c567846cfe944bc7c05d5b3fda01f74cbfd356afc2e70a756aab192e4e43943289a8275832979108e214807cf8ed6885604926206a2e66694cc0e6afd629d564ce43c5508b0570ad2bc44bf5cdbe62307fc27381b11de05bceddacbef5ce4ea06a7fdb88329ab3a3b77b8598382e2c2dbd2f827ad5e30d9f6eab75a69a2cc770ff12ba15162ec4e875ecdbfcbdd5ada7b808741e5e6a27f844480ac1500f766b414d82656d34154d295a2d9a45a8668ba5671e945b0847de9ee64dc85f76cbfeb75da5b0e895642c3aa215ea351e3b8b4bd816802b5b3c458c1cb355f688ca303db34a853632b9f22ca919027400dee6f012d3b20e0abb81f3ba6277e50c5360ed84a5941027922f49c342852aea5074444e2514217576ced2fb23b998bac8f8eb1b8cae496238e364ea6d234f57f655668800357df8aa155338660ce066576f8726e9349ca61599ad7591bf1ff2fcf4f78c0eefbe9c7f96c072188fd4813102c5ea3d52158626610c34c9e4f99bc3bce008228b6176c5e59223db0bc37c72b2b6b1bbd4619617d0d69a8a305cc24b067d881b1ff408498abb0df9c5043ea0546fdb4dc73ce6cad5b92c5e6c243462a3a2fd628844e0268baa7a534e3c00f399d1d7ea75089f74ca12f08c59dfadf7dbcb6e2d397598aac0d8e396ceb7b5f53a77742da1d18056c65125b75e81aec987a0a7cd3b3051b7aee3e9be9df97b7fac7f844b656b64a1950ffa828e53b3375fc3fbf19caf256d43c10436ada29bd364cdaf9f0b40a5d9831a13354024abf8fbf26edbfeb17eabf59075cc1fab4dcda88ccc3a0b06715c89cb0d947cf7ec3a51ded1e77d067a35227bfcd9bea2d1a29ec8786a39da32ff9d8cfe028e1cfa30bdba59fe98f578a777fb600daf13b86d599a1507565f9cd1ad438b6b28ff5a0a287f42abce239ab447a9927d1b1f0b779bf940babb884e9d87c0c092343030921ef323249348ed7c31fdaffd3124a7bb1020a6ed4fd69455f5028f183ffd5e43ac2ce63ad1a66f8af145c79201884a81c84f348253eba8bef80c152aaafaf7732f46bd767acd9ff9bb77cdf487bb106bf0073176b15b50a8e1d2dff93b689b9c2fca841016b143b064956b3ff0f0bd6b106f1f1b6404abc199fcfba7998e811b63e7982d70b6b5d9119679efd7dc8dd726604a371bbad015d397173a8f29aa636a89975b3a64baa990e826c94f49069595c9a4580434b11f469a62c194f2568b3950491ce4a30277c5e545914316fb03d6852cc870d42c44e7d2e08a368d747644220ecf4f20100758d2d03d38ea64a7ab0f672d36afd2ce9dbd2eaf9356ec10af46452d3335560b6a125310e3df23c8a1bf7bbec8348da6163d187ffa5490f94a2b34933bc298da22d25052b326dab620d8613b04867f4e8d3ec3e8254252176d9f6c99b873e19aef4b347b2651253c4565c3d1eef01285c66810b24e457629355381e74d495baa6f9c5975c70bd4f7ed20bfeb4cecc192849b022526a8fcc151c9abe11b69de2651732c1f56565903f72a798b1b5696fcd7babf1b9675ddfe3b39eefbdc89159b14660e0611ef95d9f1c8b0cac741c533b2e634a771a1f60334bab6313aae15a43674c5df73db54a3e95030f3208bcc75463e7330684006f7842e5c9e43770b29cb35f48c5f9e61c66ba99bb3e5f846c7c39607e6d065f2a279a76d54640a8ccaff0d3d4400dfb5f7c4433f7a7a13307e6ae4f5ba2d838c32e6de9fdc2ca1cab6c396cd482b4d5a901b1089368bca5c6c3b54439aff2e629078803b6448e2958224a6391fedb11de533f85b054d58ea355b152f042479fb476bc663bb22e0234ecda919170af991e43f5b2f1266676cd617d32b6dc88f7820171c737df63691ea81c1e760c27ae37df9e5f9e8ee204b706291bf1321097654762b3e5c2aca3123ad359365fabf96d1877051a23fa706c1c4a06c538427587a9ea2182949883c3eb48d209c9ed8ce56cbaac46cbe993f962e23420e8f8bd9849136b1f521066d4dfb5fe81051734f2e4cfa3a05cf47cf830ac36a39a9a593041afe4f9989d67e30aa2843428daec37254fa75cf01b17bc20921aa57377546701ad5d4d111422b458accccef567307fc8504fc92d3fe27d7761ec9722dfeff5ce76634cdf361d2a3790d89fc9e72a937188ff2d0b5c6e39126ac4344d608e4f07270756af0280f1dcd201d046afa9c049ca21d8d8352891e2eb90911ccbd5cdd21c9e4e19d8350bd99845a3e3ebba2fa0e84c3b846814e8247e67c6c641641fb8c0a105b317eab38a9107edc43a1f9fb82d6a4af7e435d46fe6e6ae4a20c75ae986659b0ff40d3b60211a563134bcb14659fa941598bc28444896246be8860c388c6eddf418a2f8ba93036cfa3d158b176eb5221e7fec8cf78c90aea697405302946c80968e3f3b17d2898af28d6e9230173ee226cbd85f8828e679974c55e3851b885d060616f91cec90b750822a709207c8e72854fad24be2e9b6c98c4ec636cff41806cce27880dcbfea9f070639d29009a5702c54b62071cc3e6dec9cce0671244c7e6c944988e392db4c47c5c24b62c32a80ad6584284bb2cb2b712093c9121caa300be3a076d39dcde9a67a1cafb9b9d4d71bd163e4ffe0e9ad9eceeed26f9c41f0b0d28c1d75efc40b9e1e2aab2f4184394ffcfb881763195abbec6d4cb907d920b999ddfb9892f5e3f625529f07c6e08be3c412d716f74604ca71993535e9205be006cef4d8a87151e12bc58ccbe1e332e3fcba04f76a7b76250ed8c75370042c57b6f8bbdccdbf2119a837a5a6116e65f85e3c1fdbf5653cc979645b86ceb56b0083dd46d45fe0585b7b459aa12b2485d39979df051a60b07d59586fbb77fa824243b75dacf1936860da7b8ecf0571650276f829c2f434a5aabd1481c3b9936899f6fac25ab18d4a515a9d7f83a7b55b3a8c9efdc5789e4d91692c0309e1a68ed2c2587f29dd1291e3c5eecbc068748b340a32a477183843d37a62fe0d5998280a37f106ae56178575fa6153fb9b28676662fc9b5b089353fbaed3526f8f8cef97d68bcc5f4d19e85ee43c5d0ca588281961e12fa0ac8b6001254c0e65e3a9292c5cbbbb0237d06771a4e5d52b1ef1d7971958af5bb19cf2e93d65eccd73da4cecb840966786f03626a8e7bbb5b42e7ba78295779995c516063e66a73d426bdbc7a0a2f0d7477e9c4f813ea633ecf715b09ef4223f5bf2397203e8348a4070803611028ee24faaa66bb1cbfda31140b22e165a75d2543ecb6204c39ca2f066d4ce773f228857c03be4231806c0e1871216f6e86e9ce26fcb042d536364805368e69019a70bc88469658d3c7107dcf555c0658332a316319382948c5ecfc2b12fcb508bde79fdbdaa903ed7c6cae60e2976da92e305f1f4cdf57b92e490b111088309b5e601e9ed09c31ef44229d4ee51781e86977df0b6e77278a2cebdc379a9a0e4d1622250cb956851831b71dc4867bf15c5ead20857f4f52290e3c031a5d0f91ec8b89a1596331ef4708cec5d798b765c50f9a14287125f0978ce733289b3b73d4af9bf0d486c124644aefc94229d123b650cf2feae156927d2f92a8916bd51980fc6c9cf8628e007882a70fa833fd911a6e77c81fe31980921dea1a657b91a53f6269aa4119ff18d8961dc8a27f13dee0582f5c8a92a281d308eab0b063dc2d4fc273e1ca1da7674eaecedc6fc1d9d0e5148b96ab02af8b75f832fc0424d74b40f970cd97e76f09bf3fe982c7eca7e24e922882368ef75645b55b8018b97d02009fa1879ddb275dc36eecc360c9676f9c82775e187cbbacf32657ace3985e27d7024e8b79758244cf282118fbd9038609d5dc13b8f7d941c90d0eaab08c49de639005bc33ec8c8ef612e409a61e564d330663d8a230b7f86a0eeabd9f62761464d64c66264606b647359c8b088f72110e94bf271f62f958688c504d6228305a36bacf98791db5118fd93825b5604a2fcf9631692eefd7cb5097cdf4592fc21e09a7726af49ab888b36a4d799ce79687a8a252eb14737d1ea1753f1841c1dea0d4bce77a3a01fadd3a4871f977b60f13526580b18f2aa47a9152c1321c01bc35fac12ec03727ac62fbba3c39f0573f7a674338d064ca73aae05c0ab28c80e54ab18d09570a4acacd232fdb62b5a0dca9a09bd29cb1d465fdad0e3357d8f4efec5593d5264cf1bbe26ddf32767c60fa016b95430e392def1a7ff820a4109918f26886c946c56fb96f66de0fa44a42d0b241da1fa6c5eff9ff4e5eaf8c3c3db0f138243bdc6dfe89a1176aea112f46fee8e35583b8d72fb825d5f5002a4524ddd83279bfa75d1a75326705a42ac5cb6495f3aa8568c206d7bd3038256d51c9f77ca3be9f46b86fe9e4ac2116c9d76b639288378b42debfd18274f9f3f85b1d2c8b3774b27967b56fbcf7591c901aee5bf16f65b50f7c0289563883eb3ab61761cfe8963c019ef17fab5b502c78938c6548757114ed7c447487c55c60e69d6361c76cd55efe71c034ad1f0f716b4f5a8c119e48af87a1322e4564d97bf760598378f9e7c74df1ad48dea6de7e7e8552290beb08c2366b99a850964d335b19b9a5b1bd8f6697ec92858951fa52869aa4c0bc366911936bd7497c94cfca3fc8504852c0739630615c4f3d0fd2ada66a9d2c70bad07de6add95d14c9aee657076397804a0df99a81333883f6f783e9aba8c74954af60c8ba3157d2e66b06e55ed6e25ba1f6c2605c67edfa9da316bde46c52cf3a9e0451bc0b8cfc4bdafd4dac963c38261f3a567e2588e593bcc8d30bfc9c1db58bdb18d41978cbe8d18039e8d42549e1ec40408abe214f906c6ccdec7d3100bb564078661e01170cd18ffd7ff6b62874f96da78c5f3626e83961bee43607ace46533dbda8b454eac440fd2e4bc0ce621133a9190326389c2f8d706558bb87388f127bc24de4b8176ae4f9429f8edc5b22d82b633ca59678043574ad1cd39fd38f7bba0b02a597f71a0fa573752149409714b773ca60753a287becb87cef7276be55dd324c260cdd3302887328f27f73fd00d566d944e377f9b8741ec2cbc86165c5851b5d17c21c65ad1c5ec56173e43b665f98662543f65bede7d6cfc927e7e2601d468d3cda68746a33f34cd8a45319f2db53df3b5a2e573d74f786e0d5457fbd75c16ad1fdadb85e03a764b9e51b853540d63e5a5349ecaaa6cc64fc41aefa8df734f2c27c33076c55773ce945b2d2c6d65b85ab8675d9b441acd61d6a45321d30afeb71e839d0006d3875b295ad80e23293141b8f1be553ba3166ee7ea01c65b4f9a95164a650ed06a54143a3632227b1126502c26c8eccaae6cf41cb3552441ec6ddcfb26fd4eebf04cc909a1ebe6ce441e4898e7556d18f0d4873029d1ffaa85546673df9552a83503f6afd6d42d685c2b7b434698ed8c81ce3651819b7a01555bb92ff8d8d82a1ff9f99d66115fe3d16f756101c871ceef5a8e3d2ba167587679f4c9f96104df21c59bd02b5f898357189de1bdc9184806dfc2332da7cd4f2f9f659d1f91726cacfa7ad439976fba1b1caf16743c6b6a8cb2eb0a01a473dd016288639c59bcfff7ca9029e0f4a4df218a11e0f5410e7a278c71dc6ed76647a6b40bb2a29347074a67004ad5a39ff86d7dd6af20c37de9dcd5cad0c8a1899006829abd86f2ab7a110ab42861349f67156b5cbb9b1810a1614379cc28777c02a12dc32d18f4abb8d2c7c941532fb67ad3780024a0818d80dbedd01e256fc72f7b5ed8d864f237516821fc7a8ea5084d270a7be1312053e8c407f77594bf60cb8d12654748ebed89f004c003fb2fc89e95e9a5934a00e14bce8724f85f582ef8614134cc8c318eed69f6c09adba7ec1a26b2576779142358dd99f8328cb671e2f7f355d0384a9200e8b11b86282f71be6f32a4bb6ee7cc2e640c99a67ab564a79ade7494a53e688e461cb1b7cbec4df3439e8db8f0354f209bd332f5abecaf8b610bc18947fff1a192da42094b57c2babd3aef7992d5ee670e03c94a5427be84c0d5a8cac651cca6d0a93273cb908cbf3b8e82b7467a8bc711757eed3408e0c521d0e3a2bf16b8e41c8f89c2afca630dd25c1573a2d73697aeebc4febc7a5fa58d35ef1c7e37e8ea9b18848920eeadf9192dfad6e99970cdda85bb99210f40493378c8e88284e6858d7188d9c33f73e11eafa1da697b485d232f3a3f9817f16e8fe4a3ab85c9045fda549c09f4e659f40b49c96f9c46ff8ea2255662c512ad8e44bf35a9af73ccac596c45cae38136e6179fc1b2e9ecb75f45b4c526a2dbf5235c6fbebe7cd4da098292d7fe71cc77bdacecc1b0df156f84302f0446355caf88081afb7619454d669d532008d91f7f538fae84d7ce6003b335d9add9b5b3467eaf20637f0a05bf0ee5632ac4c7f5ee7cf96619d86371fd215bea1a22a4ea0211ca5085ab092f3ed77f9fe4f41c96171355a1c3d7310e65339eb060238f0314e6273ffca38a50a6a228416e1c0a2444c32d18152b9db0b8935cb317fc1be600c3161c55f691885e6908ea6e788b449d4d9b2c6aec59e8de198b03220693a86a6564e5ab664c0cea905cbc1478358278834f30ca2277ad6065aecaa8b25ca4cbdc1539968b7909e9f649fdb8d8b89538db9c08ecd88355bb5c10144eb6bfe809bcbc707374fae4bba38c24faa6b655db945c73483c30084ff37e2bfda8ecc85c9e659b8791fce5a36da65dc534bf8e01c3de80494e85d6b95d239ebc2a695eb17503e4c4222e937970792e2dff2e824a0be1a26f7c62638b5dcf17be497a807147062203949859b820e18020437688c38274962946c3250a4655c3ce270e13b8f9ea27dec31e450b7de2f6d26cd706770c24102932e26c8653cf68c6ea80349fbb719bcd9800ac9946f8d64a66ae9b738c2dcd53965e4bdbb06c936dfcac60baaa214a6555195df956b8aa8720b8bece005e943e36d94b6055519b5f7dcade0d72ec0c2edbe401190fb8966f119bdf51664b7e2dafc3251077a29dec44e61d08e34b3eac5e2c1bacb9696284a53441164cd1b44bb7d64bb07b388c6695984b33b2625cc6897bcebe1161f0fd3c33372a0d69ca32ca2fcce9fbbaf7a501460fd487d7b2fb644e6e29726467d2482d6afbfe775ce4bd4c76937040c5357c4af51101b4c523353c55ec2a69dde157ee39f9f4f3681fe90299c5615869d556a2a73aaa71f16191d54e440ff61c5dc97f7b20f3cd6e1e8860b6ae537cda7cb62b5fcab0475fd111f0388755ca679f214baded79c7c738e8d4a9d06949f89a6351d613b6ad85acacdb9baea3f5e2fa6dac7ffe3d72e8af506208601b89b844623f9dfa6b51d21951eedb2c23ef3ab9bef2aef6f7735fbba049fbca9d7d8f75aa663883817980d2d900723ef0be8f419d5e2c4458235fe2b18b03781de74c4acf02c77ec3efb1b4a919f82c392c7c16d591cfbde8b3cde8b258b3c6242fcd630a11ded1e7840068ceec594b02904989a48a29904fc6d323b59df50f33400389bec2be724ffee4585d2805314ed9c657c22391d5b1ba576c2fbc71a705094600fd5f63781ff886b9e189665dbd6c7b95a6a4c1d142d9d763b564f0b779a472de2874b53552bf78712dba03c47ea361390165dfab0565401d4e567f7ae7cb7e045af399c823194753f1c0ca62df3387cc2abb1c59183b2c180890e5a023ba60740be90a256e12bef7dc3227f3e4e852a3d2f4f87cd68232b9cb4117b1d972b0ac61222b8a86c975cd86eab2f4a72b54453cd6a29f3432c56872683d840809cdc337df14371180258dd0c676012ac5821a1659caeb7625f50adced482ec845eff6b7f58f7fbddf8a465d3bed1e1a4ddea618f82e2dccfde5d08a44368f7081aa5e42505f6323ae23a264427bf21d8dfd0565a23b1b56a7dbdd3bf3b71a14dfa60edb269688a3246478f2687da99fcb9fc23a9d8e29842022d1197d9f4e91c66bc43b4b757d4726c5dc988daf41ccb557f35c5cb1a227217db2d4cc22e88b46b13ef37a6b36fb2efc2deef1af2e5ad5bfbd4175cda4dc21a7da13f06db553c6425ed73372ba0926217a314bbf0821ec6f74efbc4ed8421ff7bfa2fd53fb2714e81a3f83e58a2a59e452c93e0952f2c34f580955098804879f6c568b91126d3a54b1968c4ecc58836ce313cf34bb2b293bd1c3aac61add15ee2a7e9959015193f174c731ee6a5aead05abc19eb5402d4efc3369df4de7778d01f46e676d0ed7ebd63203a9aae2b303fd59541a35150dd742b276d5ace57214122a1b195edd653f430448559234ae5874b49940179883181f3e56e489da1e39eca7f204a8309ec7753f7b0e6a8bd4c1642547e4e1c1703bec2f33178a173a42c9fb95e557bae88f6ea5723bbeecbfe42a389cdd9957aefe46583ae0ff5fb6e1dafdf07cd164e574535dc03977306f5adc724e976059c0a887a5afccdf99f005c81724a38dad9be1eb0ed661d98d8e40476506a6562243eab462cb529d85afeefa49ad0617bd3ad954e20fda0c2ae3cb1c0d4e3534701414a6307e77a4fa6811a2d4738fb0e68ed22af457cb24f2a303815670578c64b18c073288fbc48b4af61e07933b4076872c26686935ccc54462c59fb05daa9d11c7586052637b92192e2bca94b2153012710b6ab41b5d307e49e239920d8553e1db7ba1c9dcec9094a296ef162de8baab1da32776b2f8d04b081fa4c6c6ef28ce945321feaad73607ec5a2fb685ef594b2fdc881694f5e7c436c07c1c07bb9f987891bbcd0d9c98278a3ba6f0a1dddb8cec58ab95df832583258972ff15ce9542b8eb391444457a824790803791d1bc2d0aaf4b4c7ebfa5390cea08ab73a868fdc11801f9872eb5edb0dfa2d7419d730a0a27d2967908201a20753f7312ad314d8126775915c640a8ec814115f175b14fe384731e49c939a0ab220b4a1e3c34c73c21fd9c51b2c766f3ba66ad505df1152267c554a51507dac0937a5d8b2051b23066d4d04c78b1426b2d9e28bde149ea13d2992c6ec211306f91c9008eb2d5d43720025bb5ae2a18f01591e84ed661e36ea5e00ffa20a8d3547fa6ee19539692ab4741d696511f777b91e751f3740df9040ea0d3901247017e4c936debc99c6b0489cf12bd49d05b269a03bfe3ca26215e51f44516398c9073f9dc532b0d9b04a26346998e949b9fec246a35c774da397c768c7748ccbc6da1a1b83f008cdd64e471a13cee7381790b03e91c576b26f25bc632dcecbb0b26b89f45ef572a013a82c5bb54fc397185457360ad5297f255e41bfaa9ad42ac31a6254c0883fa0a54e7b77c368b9aa17926c6c96916502e634cda91da734a356c978cc9ea36365db7ca511ce7f562a023c0b96f122635e4328a8354d22900ca5d4e4637b703e6d6fd974b2353cf7df066576d85a08e3fb5d98a13662b65b8bcc9c59f31fd83e899b092af910843b08aeb26e74b376dec8059cd234a3f2d59b96f6527dec70d2b32b0f918ade4e727c88019dfbb09022923ebfa0d4790db9961b2118d46855256ee3ae38f5f3f0293e873078e303978fb6e259441ec11dc41c9b37d48c769e64027a3ab66b119a98d2434e6e4bb90a190973b9a616817560890c34ac2c57c4579141a081027d28c78a74e297d3b7a1f2091333590f4b24456effd0b4c458ea4b7047298b1222232f0fee880c91f06c42002e49e5c0c794c4d503ebcd6240cc23d28451972fd1c3ed74125e45b2035d9daec82ebc890d3b170068f7d2c0ae43be0f69dbeeb887b530ac7eb872cd3c985950e43e2e93736a679e1f92c32bd858e766ce4b79f74660ef07ad547dd95b99c8b106d97265f52b2b82bf27f78111027064b451c0a096958223ed4e23983573604a79470796675dabff0129791fe1c7a2515bf9357a51584519575a71f5db98ffbe886a7b4d6b91155077d721577ee88a17f5bb54513e4bd088755227a8ca605e71a904751ea3f6ea6085e4cfc06cafff5d02dc2cc3c4815dcff088ab141b642cbef28af31eff8fc50761c3d08ea5d332b0ae4f75e3363e9130002d955c8ae4cd4b22aec84fa1a94e3f78d49f4f2ece989338535fa480052e74a3a2cf085ebd98e8be255a9cd065510da555d1654f41b47d8e14e9a9b832f94fc3de4bbcb00ae7827c1c629b06c7dd9a7d943f5f3e9d894e26b44ebbc8033b1d7942ebd7532cbe33dc1bc89bbad6169966237b80ee49c5426ee293e0fcb63edf0bcca490caf62d15aa69cc6b2b7b1dbbd76c22e2e539c85e94e315aea6a2d57b3d0684b43f1c2871ab5829b78b098a2236d6d204a7ddd77d0a514b02f6973b80bda8d2db6c2df0fcbf2ae62f4d50f557f8c12140d0ba89dd4b6a068cd07bdc1ab2174d1bb2496d769d64bc07a2e5db73c181ee754f5104f3cc49fd14a1d29c44852246690908d5f80f4a7a17298f89f6c701486f8d299b451663772ed1fc429eb859cd8bf3a095b2fcdecaab583d1274726c41313b90103dba53c4ea31c379c77d179f664e1daa2722ea06a669ba01ac5f5a44e54d5b1c3607bc9fed7cd3b06441616f5a27c6d828f659b86f3d6c04b95a018739d39c93a8306523aaa0b61f71771929883921ebcdcef446f4f3d68a9766455b1a2160da99bb9413cfdd9f4636ac920ef2428eeace04444433579e9af2cf7a5c7117483e800d385b399679efa203ba353b51f9126594c8c8877f1842505e57cc851d0839ef520192e6de8ae68c66a90292adb3935dcb58debb5372b041323da121ef7885e93799dc6d4b64eb8620cff59445983da8055b13da0aaab52228592df74f998920dc6f2881e69145816e410ee9ae699b686b83755168024020f2ad942ef171c9212c822a3d6bf4b42925d824537421ca08c2163d719abd543f16f276e766e5c5a55dfed94dcfc83b35587f64e654401ba3d1085bc17e4216c09167010f428a09efaa8e0e8b86b64d76024a87e8e4cf1b8113ec147687d45359d64f3e37a8553911af50b2c8ef07b11b0a16fed515f5c223d202cefdb8a8f324c1f8412649d7fb33e32106f81582ec36e3764900aef9c725d8125e80f603e09b4d5b43aa3d6ad4b8f03e42fdc97cc2292a83a0fb5ad5c0341dd012fc57e3dbb359ff1b00124c296e94f0c70b9ca1012920ab1be3a0174e24b5d921f1d34fa72c769b87689165af5c5af9eead3bedda9caef924a73de854a7ee1cfa27590aa2b55e43980291bd8ab8fef1af2bd56a9bea4237c29b135efc8234e3c936468668d23923917eca9dc0179aae3d460836cd836c66665bea5d530b189f8cef016decbdb1c06bd2aea6b12803442fbfe44ec42a5790b15f0ed08fc4a39b48195f6adb6630b944c3d7062810b21b0b3c80e62d21f107d45cfbd3e620496e0380cc1a6487b72775e729345ff35e2fb6ce01d1164290c32e894525172801b862b066804d7269c1fda277d4d3ea034520b53b71e47983085b57b4997e57deb2107fb1d8e18b18da312aabc12e1a411b8b751501e9a3ba9cdbcca15fb1c69f8f264260b291a3ce985ada9b807e9b966b90166ad71e9340dfe5314b1504dca81ea7951e41daabe32512acfe13d26166b00a0f6493a814e4ebcfc636485766e551a95d64af71d2a75e29116dc63081f3ab0884d647ae093f7827505a1f51a22f74e6265682c8482c7439874f949ce2b01a4f6ab4c1b7c30cca8561c38d2d68350bd2a6fe3e7b15e01ba3c6e41abb7cc5bc788afe76c995c3ad207111ba2614366a0b6f31f2d3756b99c65ab298d8513c57cdb4b40a3061bf0f0905dd71f67bf93e4e01b9b3155006c84ed8f9f950fb9abfda6b409bfbb0da67e1d8a82025acc879b86e4c7ca6e7b344c66d95fc1421817c1bd83bc439d1c449e6d8744249920519adf7a52b4293dd5c2b60220d0b8866ae54fb5c528864a7f6847a9a15690e8348c4928276a620952cfa498b7dd5151fb086b866f7e91632c3dcda591411219e9d4a26a7d0cc588c64aae8f32df7b23bc0e6c8fde43845585655d40073ea9bd2d20b8aa41ca75b40ef014de51c59030961cd8df5e3768a87c94a7fbcef7225f1658f94d6f95814d02adc1a683126811d024171b3fb40d6b544bbae8c9fd0d86d1dd6802a30ef518c689d05c1987629f3293bae46691bcdd25e4c01efd36a950f025f28903bf56bef687f12b01eae5d74b32cae27e1e943be7c9e305d0652d914874e6b1ef1d29ffb9ee92b83210695cd8a143400595a35ce7438b1a4497c2928481ea45b18fdf83a7068e13e88a184c1cac98ad72538208d621859642595484b1fabad113ac0c2d56ccfd5670c983133d3fae7e3d9d19ad355ce1ce3737c07ff0679e22b88833643d704620d73b5f722f8dc31eccc241559cd946eb1d90e252adaff28da16fd289226d905efa01196a445a7f6f0f76b66e180d5ce20130710750489cf146e1f22aa628df91eb31ffede4cbc3972c88edb83e39b59cb9f89e6fcbfff8d908e8b0f658c69a81359027050a014b6cf33ccbf6e44d3357f681ff6df6961373607a5a5632a2f3467af9a67c0fde2a3f255b94bf94542389a18d19f9a2ae9ae680a6945c8b3e0b30eb567e0d9f1283dd9a443cf8ee20ec36e195cf9052f399107e23de8c5a83d689b018bf5223e3ed5ae4d9f6e55ad5784153fb4d2e4580f2e0aea2ce85a20b25bb493ae3944d1eef6602e45f617db59b1f8a5b28045d5ba57a049ddb121e4dd4dc929dd4d71eb90f4b64d7763ea77f9bcee40a733c2f7715d0a7a6aad1832330a8fced26a41fc741d5886101ccb82c585861913e84a7aeabf7a6697bdfdc0d717555c9b59dedd640f4e4b917d75d550328c0052df73f04e4530af948d394370f7585ebb1c5da56fd5bc2ed9a21e7bebca53753ece7541e554b0f521c9d9b3e9066e8bbb71389cc166a33be788839d41691d9319bcd02a88cbe3666e439961927e5b578a5a5f82b1d0d57b6a005ee315655fe43d77fd3c578fa89ff328652da66d7efef896cddeb53c70b15ee85959b25fd1fc53b2623975147b1a53429856e532905917642c510d6b14191e71eb89c17d7ceb80f936d2a28c9254daa0037ea8f80e7081c2aae467a4191c287f10d8ca4f52a49285c7c3fc6d15def65973e545660d63a78b12d1cf83aa0b4697bf6383a9438d3462d9545bd8c8661d61b1964330b0ece4f467d0dfe0fd17a564998a01cac5e0a9d3c4cb29c2db252cda2754ec3dbd81d73737db630b59491c583d37e50889f0d216726c0ca4419d67300c393ecdbed501460fc370b595b28d4737064cbceb45e50ec6c591c1dac0c603c3b60bf7011d91b722f559a957b691becd399d6f38a38d0c4a45d0dc14e2a20453860d12bc0c2183304197a8e181152a5ffe9ebcfb9205d006d61db50c3fc7672db58930a2dc3c91d0ce875e2f5f6a88a9c9d47e4aa40cc5dc127b7c2506139b6f162c6d865279fd11909fa0649eb87dce7bfe005351cd4cd9e660a6fa38c967f37d6c2e539d12fefa32144e9ef24a1333e4e49bed1aaa74ea4036a763dd5d884374e7fdb753576ebaa8dc9b7e9ff331efcb4b563b9e5504761711ba2a7717061c9a2c949c8fb085bcf419401be406152e9ae1600e6f4c32ed6c297905b1eea6a2a2bb3e80b56bbb9fc2b06f2d2f23a67c747249f8e14a84de0ccd9dcae1eae06f5954c6224cb74bcf944d0e4463b2881e033b52713a356a955cf59174af2e267dc44c686da0de277d3aa15b36592712f9f8166cc376dbf677e806eef6245f61904d355843d9cc7409f8b28fd3f4b32a1148cb677cc92faaac660ce195e5ff150647c3867dbb92d6a6c2a1362272294d3cb036d56aee7cdb155a40d3d5ad6f22d9ffdab4ce835a2652870db13d800e1c1a0a411e50a601c8c26301e57b3bbf6ac4c468391ec7f68677a41909fa7db743802566247bb0a71017d2aa5384da0fecd46b7d9689c2bdc9a3c7b2ef08f59b31d9bf1e16e2a219a491a7ee908b3db9d69fd1f510de7d9510ae3d6f1b7cf740313b6aa3c38bbea687bcacbd20d86483da5d5605c5761c152ac9f142935c812855d5b10095637299403027043a4bd128257ba016b4acfb8a998a358f32bf172c38312c82e6d78cfb7fa609fd91678832b2b3e85a197269720f2d42b98e73147dc70d231238462adcacdde10c5b49d68698e9e596d31fb9008304c4b1c8c67dea107525902c5d04ecce9f1817f8852daa343856f3133b3d717b2ed69681489db35c7e02bb5bef1a5c1017727a0e0f08e730c9988134a6369665b6bc8ddc288baad9434251badc7442e92af7e9c1503cf729218be3ca6eb848669a13928cf1885e2407fc6f786848c67757b38dfc9fbd64079ab19320d54a52ccc0dde430994588b12aeeaee2469922603a4c72feb366c572f375ee11fa669cbb50aa43fa70f9961dfd27cc62e7d59ce22c5f4f203501ea45f68b1bac416780362c1d1618a0e68ddb25748a7f3e5aa3fa3f4fce6f8e9cb29eba3578be5cd15854d5a9b6b4b7dd53bf0d25970c19104ea667b45dbab507f91a840a954dc7f00dee1178ea9c0888caf52f1a084be8f2b1b43bb252f635338e99dd09e0469438d2923520a11cb7467ca92febfb1d526f9a47aeb6ee627d5275bc2a5542a200c3bbd5fd40ffe6ca36868dea5a1373c780047155c0809e5c474dbbceeaedaf73ab71cc578b39255492d725820eac8907ac08a1c7b0e71dbef9bcfb876ad064791d763f96b8380c9f63935a3a8b8df11f1b1ba18646cc47dd0e93112f6f065495f084340bf0827c691bec41abaf55400d52277db718b07699f4a354dfaffec85d3a02a0dfd6c24e9616494f00b85e5633053bbb40b2441011ead70ba04e9d5f3e7034ef33b7939010369b2c7680fc65f3ab64fdc61fec2a66c0bafd346e7bed9b59bb749b5bd1e216e88e5f00b6dcd2ef38cd36d5b17d97a3e5248a1261c44921733ea18ae4c546d8e1d73f19f112cdb68debbf16ce8e41cd2a60589ede004dd1300386ede1ef012e4b7591c786502d9b86a889969930eab650049bbbe7553b2583bf8cb2c259585b25349467ac73cee41c9126f5e0357638b97899c2b650062b1d89c86791bf5df17b88e71989fde80b9e559da805b7e9751b36e94c21f1a6abcfe197b9317027ea7679952c1fb69b5bfac3488c454a8f9f9240029ea67315bad4e83b509a76f78c23d48a4f562a903e23f971c03b318104f35fdeee30a9e4245bbcad69fd14f3179851b8ba11140a6fe8f9b700b44c955e4aeede87ee5bcee31799d3e3ae27958a3044a0efdbc9df89cb2d939500d65a7a63e1cb0e51df355b5c27f2684b322c033179e334c9862937915ea6e25d009f3f41b6ed57a3f1170e96eab53a627078b3a341092fca5cdcffe9724c41a7d43d969e41d0da7864a645218d0e65009e688b4232f8c5f8813c65b7f3a63a26635c005e3aa61e41a215f420fa8f2309a0ca1d9987eb3dfa0110e1f42915201f4dd5d548323e91c2ba8b96632644b967187dcb46d47662268d6f7a51a8702fc9e066d3ab2e26e5ee937265142135422f49032f5800739377135bd8b93c29e8502edfee12f64bc0fe5c37e5f46e227cebf30e39e52f22d1a22214e3a3f2c52b7121039b8168596169be3c805c554af8cd041e9dab1f8c480f898a3db0fb4ff194bea6231f5f19bdca2f18c219bfee922824982b55e61a93e513e31f5bd23fd91eb7a05109145c13d62e0c73ef3dc4ab26283e2048d38f6c7895b4f04b27446990f5c07c579b932a088defb89a4a35798a0cdc210fdfb52d13a60a07f2286beb5a96ba040cc0510b62b3ba68faf9d2e17229b47d3b351399558d91ebe8fcbfe2474bbc100c0d3f43f9b50b359bcafff0a61a708f4c005a6e274b4152b20e0cab8ad435468942ece17ff0b31ce2813112e26c28f81e675a3ae36f0bfd84e68964521d528bdabcd2c2983c6ff094ed4280507e96743ec0ef8333e5674d7742109e671254429c4ba511f69fba918b9baf37dd49cb9278759ba4e8c72219aa6933a1c55a963d0c225cc80d605f4e2d8b04e39f7c7c9e713077f08ff8c6cd090b9130a874d2915cabda6e4573a041ab02ca3672300bf692d41d7ab8da20c14336ea18661c73c104271057c21337c6cc3784e7c7453563e6d9ea2e0c4dda0dd9e7a108124b623a7d49263649049247e5f58c879609120b4089da30e289db81d73d591af062a288df44a0f847f881180c3cfd1f40dece903921b4709b7c3cb81b2a004fafe713ef60a93aa47498d44d942cd5fc2971da6d50dce61648fa8765e211513c73ff336e5e163d637bfea7ad74ec00397e9f232136a0f5b2b64b97e2d313862e80eaa290e553118b15c874eaefd62781e4279206c01915a43df29467551c187dd5b29d1d8d12ce060eceab7c46251ac7c901f39a2e369b1c32920e040440d1d99707973cc4b17c7a77b98f39a5f28dc340324950c33a662fe0e3d90c7d04826c74560f104d81aecb8aaa724c0c1a104a49c7695f6b2890933e3e55af2058d139eab9c0f3d2bd0f0a614734c5e149a292b752f15627035d1e5be44653e6c1edbddd821641a658a5fe5cfe8c30874d86b500c961ad031e058bbd4c1992efa7276ebe8d4572683a492450c5018b092f10def760cfcd281f643cc4b0163499953e2d3613242a905fe0f4d7c5ffa5dac08f1475202506697a8300ec7c3e92b6907fa362ac21a3f65e554e7178bd492c86813ce9a6b1cac25ee7defc37bc46b2f85623cf774786c2fd43a821366d77256f370ae8a4ff85c0d20cda787030be6bd193a67298383ab530e0d255284cdf6e56d35f4ead08bb6726f1527e34fa71e5ef0c11ad7570e1af81981a6550e93e8a337afdbe4b96ddf06a62687f412f5bdb316912ee0c29a77c0caf083accb4899a3c9409bf3d65eea48a8ab30b4c85594ec4e1def3d9ae1024cfa722e4c51304848da766a51cd2ff7f7992ae3d3d5a75759cec8385cedd3355018421eed4dd264f9647d1293ab1f0eb96b0855e53767bf29962898f358213c09854930b499ca42e1b6e98bab4b77748167a96ee61c71be52256dae9e571386d4162e23a306a3e0600dc487bd6beea16fd8e2eeceb3f58c2c4ae203ed5af49f6a6d23ecd529a13b75dd6d5a9818509dd927b8a0b6e796519dd91b6ca371f89a0e9f4cea4b5d4462e4530cc6eb88acf0df344e85dbfd4ad6622d8fcda0e7774a9278409debf3d2a766d24ba1be57b4ac428a3f90cc64ae2ee8fc871f8da363c4b6684ea364e3d9e31eb1d474f3d078e947e847886884bfbb53d1a47bd652cf7858f256d8fabfa10f6882c46de192699a549ee74a45ec08e155d912dbfa6600c00ecd4e7482dc16a6967fe4d5d21d043cbfdafd72b3d548ccf1c249c52f776da4e32389ddb112abe8f100208152bc3c783f2c759d8d1b3700b0552eeff3297384e620e9decf47b0f530c30e1f1375a6523dce796532fbc6c82c78c2098b77d23a1130a34ffd814397b6149b83c140510c0203a5c347a6571f713e826ea837bc0b84d24aedcc81b22398283ce7a0e568ba7de26ffe54ca05ea085fdae82f6e750bbd0d4dd6fcf951073631ffef14a1928a85d24891bc0d469a6f6716b4f66a442bebac5f92cbb85b25bb8565de0210da43c6debe3825bb787ec1a528e67daec13d42d066f289f6fde483a2ebb1f87174d0b1575fcbac62d7e057932b614049993c03d06efe2599c5bcd54fdc5e51c8a48274dd8365678f60728e68cd92e74282377c9702c7ec71f7d7ee7d2e730fff6edf694310c6c45c16abf6d7d1f6a537ce3fd5816c67bd665ba820997d7aaa29614ece23d0820a3f8c8f8ee8663fc97888ea5ba14058bf8dd91c162e776a109b52464b90b95cec8bddaf7bf9e35c992844a190fb68de059a98558fb633857357c8866912c2c45320fe7d90ee1c59c9ec73e94b99022e057528560e02c68b46f31f1155425cd9b7ff4dd774c27e7d9a46f89d88b5a3a4dd9827d6860aec306b11f563c436c13c8bb7df125a03baa2398363c6c05f17827dbceb7f0f2fb6f1f90cb8b50334f9d83e078b0a53cdea073c94405782e345ffc163c12b643772d863a2f73881bcf241835a0cf3d4678bff59d9cf4c7f8febfe87c9463565089ddc0dfd3d9c0f91fb3439800ca52754f06ea65e8d6bd5e7589006c21a655075538ea02668a1e57143e5bfb5ecb2d5b3fd7a841b50260e1961cea9d526aafe8103d3a5263a8be8a657e7f7bf932bfdd5dd9f6e56af362c3246d227a515b5c98d1e72138b0ab19469417c7cf7da55002bcbc89e2f6706dac3d9a0ff2004dab606a6a06bec05c5834cc05b1a37ab97b33b4b9c4d0f06b681e8039db191c6289821a67fb208486f0a07b510183879a538e501c2d23dacac635756ee289b1091aa865709b8620f62f30c971f31c80b20b79229eb506a3e0d3029e68bf9e31c00f2141ed5828418488beac920fda899d019d8d8a87bf082ec8915f1f834e54ff193931b569cb6730a8e7dd3805ee8533e0fc0a7a41ebac23dd74d2b42920fe4cb95c3836cfa3ace747a10ccbde6143b157cc006d391fb5cdaebde5c2adf3ef4377a25418ceeee14fb6fdc275bde7b5226f9bad70ad85a33a9d246f533faf2b0b860dbd9bb48af37e8df936706114c78b2ff4c33efcf5195d7518a3d2de62a59a645bf1f0bbc578d92393ef914fc5360fc6ff6e9871b8dc9e0edcf6bc7228a22518a5b23c8a9d6a66674e8c7a09f3485adaa0484c23c0820a21dc6a2c9acf316c4d1628d11ff249202e4f433386c284a37f4f8484f7081bf9583251e7bfa71f7693eff7bf567003b8c9a5253a56c371f7144afbfcff6fff0da803156f52a1525375de2723f752ea0d5f3a1fb7d5df480fbdcaf12e252fdeee501b35a3f4d6bac2423f82438d3f9e2a828f2667b1046ea72f2034e8a890ecf2be56cc101baa36cd950af69fe6df15422668ea55ce0e415b9a6462abc28e528400f770b582cdace4e7bb59707ed5d0b8c810f9e6afdfae236e4fbfa6ad79e7735327a63b428faba621473f4fa172e35e7ac55037223cf536f86afd95b7a97501f0a5f17c07451d5b8247e5674a12a3e3a0e86074526fa796ef9fe94d5473ebebd39f4b28d635c86de1eeff54e02cbf13b42ebcba5c79658aac8d083edac62866c04e6801487b93696515975c08fdc4ae3581ec8b920eb63d8b11feda0901493995eec1b13178afe3ca0289804c75e51d5ad7a5fe78ef52640e35a7a0a1b1d027a0280fa9746a0269d3d577400590b3eadc5862c5cd8bfeec6c08b810dc43a80864f587b17161eb9f6b1988c9cd62628ab3d2074c6fd9563ac760c2448982e6c21a065d8e138dac91bbfe0ccdc0e132fc3df8cbe6f0898f259ff9667a1686f7438a0707368587ef271c8066b46bca06a4d42611a2b90bf5841e24d62070ddefc8a8fbab4d5f3f945f2d185fbb8a736e190a44b96213a8251148996c86c2f5753cd35c7813c4d1dd86c1a0d04c8071207373144c25a18a0946f86186876bd3050c1a8e7d55b8a7063b9aaad7987fa16db77369c99417a6d99ada4b87645f71825168ff31f46fc65ad59b8cc7b189464e7a7ea5a46c6db1b15e537ee672ac8c1a0b6dac7ac3cf6f5b1153d7075688b6a22c9e219d299b4bb59caba660eece98114d7aea6495f2e70394a16275a3dd15b7328725adcfd4cc54f71f1e08f91d8a464ccdfdfe582827979dc133bb06060679796cce838c0b6e728cc11335e15bc37635a110e156b204e0e2f70e5b33109b60c62e382bcc5dacc017147c8ea1396495b560929174c7e712cecb44fafd485ec23537fd570ea9749633b0310d0370b8809a4cbbbc0d1d0603a6fa999e692f08d8c9b23946db2f17e0e73ef4905a9100c354f16e16e83fd63418772e3a3bc0f7ecdb064e4637b4796d66f2b320ac747bbd86129a4f885955a6d53d3d4d1b9a561b814cdb0c5a504873ed4a01f18d40249f1eab195fa09cc777073b1ae63a8060cd308edbe986f352e231ab7f54cd41da3a9c64da699175ff972305fc020633d9a6a3ada1282d2479f18d0caf84ef0ccce96eb52fbdb71e4a0ad52d690496a27baad9c40227cfc997f46d9fa1cdf6c0effd1fa1e2f3707ebf92998bfb1cf3b781bca465446908360d6f9c2431bb0f9879319ccb7f319cf69a38adf280d1a9ca75b9aaec49b3bd169a8edea515c89ba196cbaff135b640b6919b95ebe65ee4cbb1f6b0be07506be3d6d368e73d9b46a1b9f026c4dd0d7bf8f4a9ad79f0593bd11abba11ffa48c06949ee508aaaa81df887d514a63b1fa357e66b55024d1741108525c341b9c6f8e7eb06f6bc89eeac5b07d3d7f9736709340fd2b76294d98c23d08babe76d3f4d6a0b7522579ee5d7af173b6fd95d6d67b12313efdd9197fd636dc345e4d2f771d873c8ce2e3f7b2d6a7c18876652d58a8dcb49b198e799ae38293b6a762c996367218cebf5d0fb3d2de8d4837a1178290051387c1d8975c245e2cb26ffd2341a186a25732b8a8ec660fe556a6fb8dbfb603562ecebe136288156862b76e1028566f42e5dc6bc2aa3e4010d9ad58b6c1b912f78a0a8f24b2e7313d58bc0a92af59cff8bea75c0510f9d807db3d67fd871f1cfb7d9002f32965406315b5a0398bc96fa9accc0c310c5267d783c41ab85885f3a7e6ec368d5e76d84e61133e3eed213708dc677255a34e6169cba3825b488f6854519ca21915b2696a5914fb188ff07b76b7f78760564d7994bbb5460a11501a5d6dfdc540e491c5a12122459dcb7cfe53d620ef8015c0ba9b0ed199cdda18f5f4f632e72c066e26b9fc1fba394fa1f0c7201b9d63e287b736976b1d1aa1da729a128ceb657d48e0d8d460a491b7dc0e3bd5406970c8fd54258239d56865427e4916843a9b287d2729005566e94e0aa3f396391e0bea035ea9013e1fbc60aa50148a30689ab542e2f6f02b1e247afba703955d0f49db3110aa0196b6f0947c4f5cc3cb123dd789ad22950e90a383e2add72932918f6dd3eed9e4d5f9cf394d3be779b6369f22ce96b7c4c385d6eee96ca3277590268ef74d6778fa665da9c69461174917dceb3322c792df1aa559f24c536644753b3295ec4d65677191b4f9ac594d05ebb0f930b77a4a5963a92f610f6b72f4fd3bbdeaed4a875cb3e77a2535a074192c2f777b64ac1b9f3069bc7c0a49fb795de9b1758f1d3b98ec9588584259c33ded2c198827bca09a8cb87446101f581e337cc6d21cbf5f3b56d1928c1b0bfbd8f21aa737693d5795fa2da6da33db48745ec3278f29087d7892fa6e91902c317729cfc7f7ae7eeeb07f16065f6274db0c8acc894343b1c9c48f943f5335948ddd906cad7c4409237087a27d64616a49c37f8852e5f99368d0f32e1eb2d60072dde1d07c7ddf87ed3e984274723ca636d0453a077e70f017ab66927e8dab924b61913bc343e0c2ea35bf1cf1997de36d6776ce5ff932aceb94cd6a1151bbeea5ea739bd2257ef6816e08430aa44338e301c95f31dd356596f8188c3fd659fb8017c5477d8157181fee51df4011f34689cb823c29bbba5c98305314f478dfc3a7f2a2215f693f92543f3ba466f172360eb8616d398b71a3d0eec782871fb3b4810cb705ef7094d682c795e286b4d939333f3e20651742641790dc8b4d161ba12814f39cc76a8851a9db81c76faddf1b39ac8705ac4544b25f2490566091f792f3acf55d1ab1b9f49748ac77a8f2f73ede453812d9b2b8b48b83bcebaa8e25763f73d77bcec74f7ef0c43e79f9a1e4eb8b9839e36a000061a4a1238ee0e3c54eb322845df027e0422831f705f56931e11d6ffb8bee8cbe6897da1ede3ce5075776c6fb2118c7f9a2e28c66fd3750ebbe25b8ea154633ada3f6388d2a5f3ee0c92faa6467dbbdd7d74dcdbda92f18fa7247011cf4ab9a2f8a41f424fd2a804a040c6e0e14347348ea2ca3a6a7aa39c463fca55f3d2f44749e025d4dbfbc33dac82e459f1c9ef75fc68075aa41cae32e8ebdd5b27e9d686808425cc9e7e78664f45252329a03a862a9288a76b4b372c6c25561cc37699a4ea93913cc3968a8689cd8fc642d2dad22c7f649ba03d8cad908e1caf8f15115bd232ed11627db33a2faae630860debec1b04dd8b19babd3b8298bcd4dfea56db32bbacdcb4214769883acb37e204072956d8f4de93d9a0c1fc427dcba04ff6de20352fd92f929922b75dbcbbac943799a3ca15efc73710390f6e612436cad3198648dd6e98d7d2971e48e5eafef7d2d4477be87f82b8a442194dcbfb472d5465fe910c29e7779cc19788d6cafb16c9ec24f19246f69b0769680640ab7d14df35dee47cbbcbf251884e6f22900c6fca1d7f62a1cb369f61567536e4e3d72cb50faadc2769a44baea1a4a477166e43a82d08c2be11523f77988b9eb7e15195d1e61d5e855a298b2efa2b3fb87570cb3170269ad1d4f46e3163d7845c1d99b7ab60d8025b8761f77ccca7b563424ced1d0ba062eb07bff1cb79fc365769026eadd8651cbb7b618415be4176cfd32afc6b8eb0972db1aa44557a4b0d008ad9f889d7c4c8af577a8d0ce3bfc0d46e85214e00bfef22ab88bc8d7f63843bfe8945ab85741b3db3ef7c35b9223f4b66a92cebdf60bf49ca3e75f6e6344bf919d033282acd269069203a9338603fb5fc9289e211826a1774fd13283e5b28fa477d05fa354cfe803093e25c5e9ce6968b8d33c00ed45f93cb415810990afc17304bfb51af04c024047aeeced5bba6e9707707ea34fafe87b9ed0fa9867a4a1074391b4cccc2df13e6fbe4d87cee9d00915e790d539abd0b5d348e258924420b8fe61c6cde099837f7033f71adfeb3cc8879f6ce6cb2c32cdcc2b8ea59c1826a09f422f391b3f4402c8cd57cb8a527fa7d87d5cb759b95886ed1505c68f9e6e29569ee8963a1fec9d7ee1bae3931811fe42f42d313e7fa700f54135dca5673544b6212ba0b7833c598942d244db5faa809cbfcebed6a4e6913aaaee7df4f81d0d733ca28688c438ef267082a6ff36aff6f0070ce5c86f660780ea5db6bc72f699c7541cc67dc66d682e1ed394e3bcf66d5e52845d8006aae31ee0c8d526d539d20823e34bd90b3f81d4ee215c54b6099df840c955a7a273028994ab58305d551ca6665028d940e5d76bec527b8604a0863ca3584be5da7d12d26d7773f200e5dabd8b3184c4ece49aab10f0ad940bbd866a55327575da2245ff3f9287dc3db067927b573c13c13cf69c8db7089ab7e43dff9606b246ca94c940e738efaa87a24c199ff18c2815d145cfc1143a49f03a43553d3271736e813a17b9c349d26f0ca3c36bee43caf8dde6d11076247fbc0bf0f8979c33b4a1a115b5d63f1a536dbd12d05839d517d6505a4d3404f4997de3fc816c05bae53bd4ebe911957369e4d8583c5b07c15ab5e981a97936f21f38a362c0319e534248c34ac82d6e3b417f010a50995645d2232fdcbecaf6d483990d483d7e2864278b92a2e989f159d9c81a3f265eb3234f049765d8a1ba9cbecbe94b2ccc95c22c3afae55c237bfa3be80c5b93867fd2c2090f5fd8a2806fd8057ef112b94adffb6e4f9b788090a7f2233e8813a684742402c4f980309eed1b70358e88efc61f440ecb2c69733089305e57f08818afd9cd55dd23b094416c17a955de3479f233ce2a906e23156614e2bee823568a78226b211a69bde0d0331ca42e9b4a2e777533648bc3cc726b4ad1a3bc57a73e08a668c1b41938a93a628c0083dd92fbacc1a849c2393385feb2e09d0541f24949ce4058f2ef43232f2cb8b40448d3ef2c008cfe668c18804c9e4edb9d7f8b7f3b0274845c5df0132cd288fe40daad8981ec50a2259834557dcee54f21eb18033540f22d2b82ff259339bc6131087b5345e636f4c2bfb87f5aad3053db91f282979ab601978263c87c76190a9c72110e0b1d5801928b42da654f5cd7b2832c7ec2b957f3ee38f9467dce0a5c52ec51aac966bba2b28bc4ade5b31bf8f52e071a403ab75e52a8cb41ebcae5562a5723029609a3d920f10a726cc24d35cd1df5e9b8b44f4a25687d3d43cf4af0078e684d5ecccae08f9ae66e42c96ff404909327fcec86ecbcd950658b26f02c678961c14f0d9280c606f0dfcd310dbeae45f4cddd3ee3415ebfd9832640551ff731eb14ba9764da12f92e221eeadf3534cdac6baef627eee8dd4e39e58a0177b1f5765b2fb6163821148deaea806d3101fcd02851106bef92a9e06ba47784e1faab1ecbe29058f50822f0e6a469d0b05a575a5ea1c8012d1c156e02570c6136976ea40e10cd2c433bef2902556f9dd16ee9c2b6db05b493cd0bfd5f8cd4cf5265b28b3c7df111677ed9c3a64552ce83e4112fbac9103c7bec3ad00922f0da00f5a7b34ebc2d1d542da88d5be79327ae2fb2d0069449aaa76a0fe7aba3dfc7b3e784dc8d082628f8e25487c9f2a6dd782e21c0be3bf02962273d2c2f987e9f6ef92ac2a2b62ad6500e5ab2c9015b63fb7dae0f1166b3b0a8556f7175399fbc8bcd288e77608f013b635fba3f4f1667403a6e35d16275dcc165cff3a8b91026cfb8dbb2c47e8527557f8f8dc04505a469f0af25a5c30c649fcbfa635a1e4be6a8e43639a19fcdd6c1b9ad3fca09fa7d1b106d02fde5dfae4098457535414d1dd03235ae05336f2ec37017dcefcf87cad368dc512d47a87c595c03f19fb9b04271fc4746dad2ee22ea0af297a7239c4898027f47241b5f9666aee657cee4b1bd292a8be9855b4c99d587d26d051112c97e0f7d3196390f2c8aa5dd2946aa2cb9d9de94ae3a7295e9a85805856aa27e47b3e5532fa12e38ec5ae4a9a3fb29818856a9300c6336617d3bb7da7f2ec373a0be55a19e25f58b711423591d9df3d173040833ff55be45840218019b1b3a5d932d2919397424b15d02dcb968f83d7bbf84b0cd3c4ea39383b9444fcd0639cfb13edc956521a7411f1a75859d4c8692b5087f26044021f5f2d33608850e0fe13696e4e80c9df5a49486855ee767593695dd4035d66cd0a404354b6e042392b02e1a624aa576720f51f32ea1c64f0dec5c9676723881fb79e8cc27b99de75df16fb7728e82ff8193ee391f8b978045e49c7f5163b39924e3e3d332c1ac6134e84fc56eb1e420ea22d2495341094b24a35fbc9238992b607878e00b380ab6a0dc7c9629571875399e1ed0bb2c68ad7681ad1a696d34ab0f6fc4bd153011cadc56850e996371d566760fba7278963cc2e45631fa66e71236e19eb01a8706359c2780763a44597f5ecdd0e3256f790d42ca46df30c778bf3cfcd89919e89e2c53391198b310435cccd6b8f148fee11ebfbfee84c27e29804f25d4d0b431d3efc385edb93ba37629f08f4a3111a92289c4d7b44ee92fb203ec55eaff58cf91cdb3d15ad1a68d998ecdd86b81e302eae40cc9d235331adef709bbef878648c6bf37178cb7ddbda1c1b67e756c909d4b64db9f01acdb58f8ef75d1b69e26b70840130e3e7c7c883b9e733f954e0ba57d17a834191d7143af16fbf7d992a99e1c4c43d1f87d634147e225e1128478cbe41b00fe1c1db4aef1b5995bc41d79a288f0467b9ca9f504f97828b32fb181c4ef9dda872a1ee728debc395c0397e69982c84727bd23715620050ce985474ea31462ec3a29609c0c4a5bb15475c18a094676d7280d4ef4e9cae237779cf499fe06eec24e96115ab897d6084ee49f554a483f2e7f00ae38a5ff21dfdd5f23e6587d817c42ae4378d4bd97eb8960b99ffd745879f059aeb19d406053b927a1d9d862d02d376765c3c421e69332b60e064b39bbc83bb152eb1675e670f8824f21a15e9d11cac0560c2280972bf296a3e986ff061c0db4d8ed55a384940d5777eb67b3619f88db9500b39ff04c49d8867a30f339a139ec355e4dea94aa3033b1c0ceb2a5deff86f051a2b82ddad87879fa249aee25e69814e09230264c1c12e75edf89fef6d99203174e83cda29fdd0c4cbc3bf7960f73c894025b44b2c9d76dd8e58894a3cfff14ef3e7a0c66670812cf4f4b9bebc217a0b8d736906e610315cfe9ab3f832ecce946e9cb9a44e0073b0b194150a652a73629654e574c98da50acd74ed262b328dd30cc3cdd2a8cf22b93b16c5cb52083ed7a73a955963fc94b50b8ae2fef04f6866d5da9f75b7b73bce0196a4459f3645b36ca430548de4c8f2b52a971a81b98b1f53011ac053d99469328f77e556e1da71faff32a243b3082c9d374460ed62bd59bc2294cdc043efc464e205717e73926a5cfbc6a1644cc20b330bdd7769a463e585486d106bfdec62793a6dfe3681630f5f8852b281f0e3dfb33dafb0fbbe3120efcd22f31c757ecd1702a36ae7a37e6a446fb7bd6e89fc349b705df1f1aa77cba22120447f4659a2f3bd330b70736fe830bb5fa0dd92a66ba9ed70826eff95fa1354f99f0a520fde350d9fbc9f24c8520fc37eddb755368c1702f7de918a618341dbaa9a9d3d4623416cc39943ae95daffa47d9f7e4a8dcd0cc955e72ceb1855222175ffbaab1bf59e88551b2bb8fa98e791357920676a7ad122f93294f9d2aa3861f32a38bc886a09cbe3761ba397dc3e1767bb4269b792815dc0160f71daed900660d5c69359be1040e9d64aa639b27d908e321459ebf0077a45568c337526883c183a87cefc1506d4a17661f2ecb0bc067694aa11e83f0b6d434a6a354ed33a03c39c22a6afbab1acd7a05999489cbc009e2ab30766f66e56706b9f579443891c33bacfdbd9864db226770874fb951b9768de29325d46659dda5015f10d89298a7047a6067cea8fd7e0821f4cbe01937406c0654a9f4f55e9a8e83ac89aa773a89dc6b82ff0f206b8a4ca5bb4222bbaa6030939b160b2e3cc2bab8f0207c9d1881e6beebb70bee995decb6b8bdde5579e6541a77f9a78ad6f6bfaaddf409abc7cf43627822079232abdcdc0f1a4662ecd79734a005fccc01a5167af71358841f8b4f267b982091dea31e34acd29b89d5fd47fe615b727cfb457318afafcf74a73ca36ef848d02e28ef08b81fd7958d946deb1ce66ca4715920bdd253cda0c6a743d33c942568408562b38d2cac12a67c0ce0995f3da6eb21f5e1ae91ef332be0c8ea550ea63a07656639c3e5050e8f01bd5160bcbfd242f7509eb537da2460455d3adb03005c7566ceea4bd075c01d99b7c73352916ec04684a9b4db9a3ea8a87b62cd356d8ac5ab7605ccf8811b49e8412c8a40320f93ecfc30a931b5d2d19972c5553aac7f2ca6ef84bc97510ee46685c1ab8fb904e28f0ba718075566889dcc38eeb25b7b035f1ad75aa3088309c1f7571ca2c328fb47f3dca8d83542af83bd43d9fa202056d9668b0517ba81079e9c81ee691c20542f61ae6dc052e3cef62a2649f5ec802bc5819be197fb954a4b08379aaf231b4e77decbc746cc0f062aa8d95314422dd3cf823653d85210371c88fc495fc739ddd1394b8f9f71c173bd37002623d26ade56c00787d2a077235c7e5104cff8ec2dabee4b577d5f1ea4c83bc059ffe0165a14819c2366a98995597dea46d9d831a460d98712c6f1c844fda65ebc2e1cf874a752c3f3b00c806bf254ff03683b1bef8b825195994a628d0d0745ed291beadf30bea6850992993469d21bf64d2db7b9d3e749eb2c4455fff53cfe897d682100b35a9e8d1fa3f5e7e03749d39608fda8b6510b6c3e19565bb99ed517bfee18f767f110ae0a1db281f90c1ca7610ad63984405767dfc4080e4c00bfed0b188dd72ef3af8013b081453b2ce337f8799cce0ef31249da129951505be4696453c602431ca328869dd6454130c607f0029a2985049323a7c482e598447d988cb0012ba074b036eab0230f7ca62f988160aefe962833f49fea35e077aeb08271094ee4438c5cb5359ccc0b49293cecd39e6eeeeb5e71583500c6242a445fb044a93fd34369b911ba1e4a1bc62cac9d461079e4755115548b0851b8f1e04eca30406f6cbd2211faeae3e262e455e9aab427134f094b3dac80275a1def379451cc8c75721e553ef5c6e70d469e2f66c25dd8c3b281a0a3bb3f8e27b57363c971cf4fd6bacce62529dc7f3a03564af9c423abe6c15d23b903e1f341ed7c8182dfa631d3a455181dc7e00219831a6d05519b7867d7a5ff44bed3b8c52855b2e3eb9cd5e7b92dbd6c53d6739c5c3fd9aff441b61d4628923e51a0390bc2a67dda3a45623ff0288c485ec668d873395fa97543e9c822a509690ca287b64332a738779d39250f5b3ecca8639578b96195d014c4b27806d1d7063f201da5bfc2ad842e61e1c3c8ba2fd3a7ca9718c40d951703bd15bbcf8da65a3a1757c0f71179b4c44166b22dfb8b6ec3bdd7838c469d005a217572783bec5641f4f19608ab3ae116133fd1a8ed141786f170b98c04aafc7549a6aab7727d2822a74220c2d4c49ac47a6190c91e00bbd153604b65135aa8a9821a1bc6660fbda3a41c11306fbd717742372ac72b0b6a1c6bb82a91261f055a209aac78a0db9073fec81ea2eef18d99d9da1adeffa0e32f1ddc9394638f90907bb5074ac0d3bc1974c9a2743595380d134482452a1267b867c4e1b2e8d610ee459c654f0c7854537d827652c5335b40936a6d8fae7dcc088d81b6cb25c57e7a7d8b937706069744a9f6b4c09ce09a78b80a9133c40ccd34ce120d3645ce464d5858934951c88a89d12344ab5c4685d85c8ad3ce0f772592283a1c3b0ac0e0af67dd6fc4bf196eb5b416dd710423681859ce3f02e7a4a3b989ba153aed3d953b9af4ce2578684dc3387bc97ff2629ff2203f7a15491146ab782bbde0c6ad28a4df48120a9fc34f7d54ccc52787d139879479f6ffe81c706fbab9d0febb749bb11112f7b8036807b5ed5e33ec51b346c95884e1eb122107a1c4f9319c1600ae603a4a0905605fb77d0f69c9af245114995beb7fe944c2545d7ac704af1ffae645cf7f84808f43b24bae027321e513950a8d34466d9f8dd2418eb3178ed6ede2d4a8c0ca11668e43ec74e18b46d52312c1c6bfd03124945cd9b67cf4313e0c83d3724ba8d549e565e17e1c537e3bc49e1ac6f9f9c128cb6d15a191da6f3e577d3b68a1ddb70cc6c4679201580f050e44dba4260895a990bb6ef9c8ba50503e4dfe3ab7e8744708b3f8031b0032379465045af5e27e5e321e2c8fd829a7442c2f1d5bd8ed733544f9ee79ccf67346575bad3951f95f254429692f4cf6327cf9b14e9278304e2b802d87287e9ff961264dc922a39fb368f11763fc629fd958fe6fb3eaedb7d9bfdbb7f4ec4512de02a6a49c3da7093392ab8f102d9caef12af65f63d8291bdc0e4ec1847fdbf1c63b7a6c14ff4dad129c961967c33dac2897e19b634a31610ffce65652872a7f1c99e01812616271e1f1ddb560711ba51c4880f9a4968cf746ea350594e1f9aee545a794855997596c7b129fb75b490c75d72322db5b056520ec6a36e0b8ba5d8d5c133401e1f5e25594e1addff525e50b53c994a742d4a43467b04ca89d159b43ca49e9db1440adc6fbf5963993fb7e2a17b7a629f8f4fd2e4aa631ef37b723e5e44ea5f00a2f112ff32fd866cdc3a6a90eb544a1667447519e7265ef5cec8b79d5f39f64a4bcc25c07bcb605a499e904f00f329b3715cfef307de66c90cc499d1102ce228945d0f8508ad6081470f83faf45bfa15a527737ae52b52a4e6ecd787f9c5126f9539b6d29797127983770eed90e79ece6323288632a9eb3cc319007c61c067cb0386c11dcae289ab8fd4b615c3ce8cce126839ca752724a295b0b7a1d20dd7e8e0cde9d29a160547fd9aa032f280c000976efbf52d02f9ad72037d5d28f37e188490a89fedce452a5ceb6411ce3239ad45e63304f51215944cb70b002504f15994563dbf74177464b827e70f66e8f759b9c1af822340f3b10a25f80d3183476fff964b2f0749a162443fa3c55902f6274f2f5af3d70977d5b58858ce64bb974e2df0f7fde2f151138a971a83d454063c0faff3e6e2e0bf6386c1c2b5bbde9192dd9f960a005e2d3cae22a40a4e515adaa898d73351089bdd295821121fc7ee5ae69fe631a7685abeba9c3814d6bc524ac5bfe0115d159ad6b15c632d56c4bfa6b28cd760893e6d4894fed21b830418f66c78ad741633680c7bfa8c1cb807fe1e89179663f1c340464ca7f9e6a6b3732372f43f2b8afd32756b4debc0a169b99aa57806629b28e1de3a689f83fd18f87a8b5a6869e058add938e3d2c09c80c7918747181c04feaa4c3b90cd512cbf57578a33c43ae68f8783c37be2b110f7ac02521e9170393216791a7d1ea133a8fc6555c6ee2468b7dfa9fb781dc3b5c11bd6102c4dea8629c985e7e6659502e0f6800c7ed0b7d6bacb137e94062a25ae572bd29577027eade738cc2553454e531adaef9dd01a09dfdfafa75f51df96486ae766db238ef14a963a122d015f1980a606770b8874ef438c38f28227d4cdbbfff8fe7359c2a74cc38b2e29cf91dc5ff32d0f6a53fc337ceb1e0f62baa9071c9d1618aa345f30bea116acf1566e147805b33f00ddb0814050a260947115eb62c7dae72e16acdd77cc8d95de9bbb5f7bc024e4f1d266c3162026b2dfffb5461f22dbd1e62af145cd384812a270393d6f3ad4c4af48c01734a12788c338a0fe2d1ad53aa257fc2df033e1c0a6e2e05d1288ec9197416f2647e38bf6593fd6ab7a3f83e75834f6d3298334bdc22069755dfdd77f777f253fa802106184b9bcdb710670aa38e2ee84cc70aa2c88d55aae11e3849989489bbdfbb1c3d925a9225333bd09a6c4f5129a6cd72e8f59207495ff22afef2947dfa48242aa7695169a6497c2698a3acea90d723a284cb6c661af52b5ef10c6fc8006820cca9927af53d62724ded38f2f5f4ab8693b8fba06c434015cc65077c4fb6f7372e2b386732aecb3d5741059ed032ccd28542d1fa85c25e6a20b26526df815947df4788674c810ceaabf8c94b2f694de570f2f4e10485185e2186e8a31cab0569ca0bc57df0c301902f7ad0237a0978fe55169215ca1855f399e3e0e39ab655bf3f1dd0025a27516b35c78533a7d6a3da3fead2a51599e528e4656eb79bad69989789b3e09418e8a1bcf67695ee3c9ead0bb7a2b393d61c6e6ea2e3b8058be51ceb9e5995a935b098a971a3b93ea2f07a3ecdcf69857c155d7649097a5ff8fdcf3feabf2e4d6fabab3817f3d77f4d987093c2aeae025dcbcbdc4ff7db59048e0f56003a041e772322c9fc45a71de7052ddaefe3709efeddba1d2d8ccbc3443b222f2832b87e16e2828b505bba504cc2c003d5592e6fedf6b39022c2e990cf5913167e9bab9337a6136b70b57cc12646ac669efcb9ad0064efdcf7d251de0d60e5e336266fa87092ce12d8735e10def11808e544bbbb75db02725efc2580faadf8140cbf2595662f25d581be6a00bc02a9e41e401c59b3cf6fb05fd892c6ba550ce44aac966950ba12b1eb4fe58881353ac3f8a2464bdf27b6bb5e017d1e96a5a5de00656749936a791910a9e82afb4cd68521386d33163796f634c54c88daee31c9b716e7968e91401f06c6b01bda6e59450792c3a87585510de61df2468d4b55ce24b5654eb701062d6010713d6f84056da5bedd73a408e327be90c878a0fcfbaf149ded4d89546a3e82943f878c407f1468855632751c7f09ae6ba52e1c5855be39b414720c24374e8bac18e606b7d4a0421b9f2d2fabd878019b921acd69e6657675d64819a6b729ca9ba5eb25776bb5d5e177ede9b0a778b5c283990870c2bd178d38a5b213770e768ad8d789b9d7e226617f0945d654f8687b8b4def60bc6b7dd16a985c17d4e889a15c2e4db6bf0365615a86aacc17972d2e6a8bb4dd4256dd9439f821244f03f7fbaa10c2a89665e6716c043ca5cca52d1d9f5c5bad2b11a58b211cecd1c5952860f6a78c6388f0e43cb8e596d8988cd4c261511122e997147d229274c8efaf45f5721a73c86b77eeb4e1912be5b55e8684f8a14ce606b9a44700544c360b34e6cd8239744345156744768b4ff7e2e692b3a4274f1de383cbef8d7b434e746091908c70895f06f830a37e53083911ec1560eaef2829b76162e9140118233b8f8b007e694aebfe49270a39d7dfc32d363b75f8afef0859927e3e0638dd72bd0f8ce9738695d803d317a440d22dee00949fcfffaee9e073388fe99920c68b579981a79f637ee30ff5b6477f0964da5d182dedfccd21d700f55880f377dd78de7014871403efe6a094a07bf1dac08500b0ab25d20a40517dd9694c12c647bb3a93099c03004a46d1ae4b43840c4a77396e907cc92865a700cbb9a46519ded4260047546d87a53c3e87659af53d4352f1fe951214498fd468c791a7c55839417745d473c2a925de0b2518b771147ccb911bdc78d8131dbfaa83df77fe3cdcb1f44c1369ac8b8b374c558f10e2d6804cacb7df9f94ebb3ee677da839b1dfce8f7b6c028d4ec65b7b14154884a84ab8dc8c651e1ef376cba569a7035d63236bb8c148342d7674f4abb2045a65377fc6fdf93b044acf5f29abd71b300ecf98149cb11558eb0fcab0d4e1b8af3072ec29377745da73c0469f9e36be5244ab1d19bd7ed0b2df8cce11bd032bbe4f1dbdde3e628902ba600c6e13d7cbf30afd8cf590427e62c96d8553abebf716610213a7259685327696d0ea7231397538457651da2a0c86f9b609cb719f3ab56b85785f17408b16ec92873a2e8f640c7561ea1ad1112cd443e324a01c1bc1e9b6a10ed1e63f3557cf764e7d6c1663c0493371f14469ec4ee7d052c774a79ec337042cee2b5344815728c3b01116fa7642917bb816ebd8fa1ddbfbce0ae38c4bb2bd0b622f4a69b680b46dd0cc7c282052fee20cfa8ea05d5676c85015981cc48e2c068b90a02da84f9aee4112b0acb2af8c88c1c56f97ce11c2032f6f42905a69a3ae7bffec87f6cf02823753c30d02c2ba55d9b845c0d2c94a3df6178477bb3217f780d2fc1cce7f471d720e46ca9ce3b8b31ba3612ce12b402a74770d3bee602901c7a1fb050508355c3617e89211b65931dc6137c31adca7296e35c991fa279b2cbbdbd691398f87e1d8a147db46ac064836a6be44012d8b1929b3405143e9b4c847c0fd72928fbce21516e24de5dc98fddcc01774ef4148923a05227dc83394fdce01929fd65a8ab1e623646fb1d2f4d7538311962ab7b5a8c08449ae803305a7beaf6b26097b391e5ae66b5aef877ffb1e2697bf58f67d03e999b6b94ca2ff4e8afcb7ae370e949ccf0005b43fd65a58f239aae2192de1f7a5934898d602c7f5dd47c49133dc31a79ee220159a7f53510584fe0fbdb2f3a8037784e6a599d88720e73823e0cd2e52ad7bb75d26d602c511230d67ea6666a2e34cded3b9995b02fbe1a439abda218d0185c7525bc5651dc1c34554b7bff847803736ea8260d11bc57eaa83d58f28bdb83b1485758709135c09730e19511d159c3228047b2fba9d44931aeb6bb6aae1cf3e91afc6343ad40114a556900e017b2130e5133e645cc9e651590cc68979de270b835593bd1e81455b5f4f90a242140029ac752739bbc182dba5246e5a6613ec7f73d61c8fc24c1b2d9d71eac0d34959f784e4c0cc1b86185d48a138d786f143db68166b67de8529a82201498590973830887f4334de160a6b4367c1ec3bbdea40071e93a8c709edd28fe64e74533d49dad478fc74773435dcdcd6fafe4b129f4cd9a3c6272d3415451da229264a3300992918d0fea9c47db120c51427ebbd27ff076833333e3cee6e8978d458dbfd88ae97266c1a3df6b5ccbe688c1db6ae8a81fcb4db45b923be3b7212f0cefe44a881c47a60cb94a877f4ed570666dcd6c34c369acca62bfc640f47f5358115cb059617f30a1747ba1d685bc074bc1fe47e4032f14eccc97cd9deace02bfe3fd099c0d12c6af7f02aec1d9c3143f641fa4ba4e33093db6336acf03f62d86cd38cfff4d254657f0004f84e0bd344a04bb5ba2ba3892d94e33e30f28e01576c6ac01596af32aaf427a6cda337dd9d95a979ad410dbeac2e507e4e12afab8f3ca46ed5b99f473ce2445b80203466d64b0fdeae81d0652b6a1a1a0d74acd6affc45ea95e723390b8a87d8ce5912e433d5e32291cb7782d559d78159baeb019a3be2fc92548db385943c4e188dc50920b53db301ca3818b9f7b4521d844e1f9056074f117f5b241f58ef4bcedaa7c55442fe1b62bd2178dd1a5c08febeec6c45c6ea96181d0f14cf2008004690696ceb8a7ed098130ce54b9d8f81df396c54d614a44b1f2588d4143cd8b72ba6e4ad7e681745775d88c12af7408d1c8a5bce6b45e73a8421c7a9673de590073a78c90a7bc80975195904ffe263b0d0addb6a7662cebc1042cdfd657e9650aa207b51f814d3fcf352ca786be5a889288f8ee2f1cba6b567685c9fabebd60aec513da39377c3f45b03ce9eeb303681dc3fc735aecf18d715114266a92d466159928e2f11edae243fa7405b58c86ecbad54019088a39bd2b49a085d4c41e87cbe85abec823a9cb99527da9e4842cfa10e2d9ead0fc23056fb6503a0b52b7320a228e4965d66d14a58e6ebf0c61dc6c512126b49942d5fa3533d699b6a3be7fab758554e199a7fe484aac980290875f71a4fc25a612a02d36707ca306e38a391cde43b46932ffa1db8ab70ba0ad2517386a54863f6dac9323bb335070d48db7ebe1711a9b4860adbf23ab81b19ed32a9142c8a5e64ae928f53aeda84050140b9db65a8a1a1afc32214a5b35fb499c677cfca2a762909134dba9752c4245fd9d801cd84ea7dbabc047bb6a5ff67da1d0cdfe352675bad454d7d4ecc369945af31e2d55fb360cfabe4a0a2a57019ecea2b8638d4f85a755b7b13eee21968d4124bbaa9e366cf20e9ddd15fa493ee8acfeb56195c245f009db4e9bd72ff794dad1716d53e421a1ec2271f4622240aff8ec2a4dba025d2f85c9da9e6ee604bb3b1b4ab168e3e63acd1bd87b9173b3748b573d8c17f3c432a4554be4f4f3753a90fe8306642edf506612bb1570082c8e95850f892bad6a17c563298bb75c01e75b461196516ecc9d02746c163a7a0ce73dca709ea661a4313f5051edeceb797ffdb0f9c230ab52193c4cea2572c7c6a34a69b9b5d219cce8c10414b74ff0c48b57ea82b86fa21a306c76cc73f41514db6408d5f83b752cc2845a23a2dc6ae89b3b8162c84145de70cb8dafef445e53cb6fe5c34e0b7120c725a457b3fef4f3af749aa39230e3b610d5eab2eaa879fe0d2b515dbc9baa7bd16d89587352f085ea89b6a920f4b750101210e805d3f3c3278feee2dea67c644275d1f28ca1d1e37ff5f121d15274456394424bad9fe2ccb948ac927ac80a7c4342ef813da3e3a17ac0ef3905ac3b21cb3e98f88a32597caf65c3fb34e2c1127ece871676ee0897d4d5550e8cfb7322367c78e6368fa21aff51ef1d65b06bfbc17f03f890ae785b9633215df6d3d2a3343b20a1d1216f6c36bf00550ab2f4105d05bf4034a4195b728de647b2c42814e5b9f4c8901c1193ff4c1aa81f37e57df1a6e2f2b46e730dab2fa19efe5c72e07fb80dd5757dc02e1f906447274b275d30f474a2990e29486b8017e2ca2b3f2f8fc30d484dc43662ce7a632e2d17119bff607245a2ffbe0af73de5b3c48e1d865cfa3dddb1216339594e0ebe19d92ad308dcbc7e860b16a9aa322d437e2922102df295386097ae1f80249e643abadb7b03ee8de6461b06456751aba869ca9179dc23a76f41fde2c735e218646db0ba2a94d2bb67fd5d1866a877c9be872cbf9292381771fff7df3c84423e6da1cc4bc230c1631407954b5736e883a12e695f1c9e3dc6fc3513b137158493835c1021e7bdfbae297ea01be7f0fdaaab9bb703c72e4a9c77042280eac91318804bdec83081da14b8e0bd7c553d16e001d54c68d54a9b3d7b610f01b9aa71dc55c364676477c9b606ce3b50d355ca83b07a249e985bc353614ae874bfbfac5d5507d85c381ca4a73dad5119599169365de92a8560ffb5ffb51a3572f28bb370305505b7a3c919dd810b4d6d11667c3cf1400e040f823ebf616d9931fc8f292288eed6db0a57e117e856bb6f4b875bdc9ad827af4badbbc74102e92ed5218481afb65f0bf29415b167ee9a88e6e541be6841f12c52086be4bd1235ba87b4261748f98e59b279d99362872dcc0116ad2fc7a3467b41376cc60831f8853671a26dc4b56aaad0efe00307b7c566ed01269ca1816e6939d0e233d1ec2e286a45a31985b497ce1d59f386fdc2e334deec827b706454c876e7b962f3b6d148b2f393166671cf6d8ce4f1ba7775257b724559598874e6798fa8353102f0d6b30747becce46ba2df4386b8a6528c5103177026c15fe0a8ed63b32b54058549690695527400530ec3d68ac284e9c9a2f3870485bfa1e4a3bdbb9a5d901d0230528cdc4d06662df270bb40a2606d4d8f293a0249169610699408cb2fe3b370fb8380de11872a88d680e7dfa4c0ddc5c2531d50c3af398d53050844d1387d499ca90539844d3ef225a46d7af8d3c7a555a530379178069b4b99b88b4b678c286e07fd2ed94d9c763d73db412301531a834cdf1e46133b6c5193a16076e5f05da691703d8e0bfc63bae11279195339f2204c93cbd29fb2bba36db0314755d7bf2f3512c37601aa3d0f8725e37085500f159096b2e089c6e6a55785203b42ba6d06daa1940427968594490a85fd798c26a9d48b1629a9efd4ad92c69400c4b43c6a7613680b1281f6053d3dcb2ac481ee47090007621965087f2b717693a7bbf172bdc9ebb078da2c0db786a2f42212dcf839a9213ec30f749786a024894fd99dd52a05aea7271c5f778c57e68b9e0fa4ffedb01cc9a1ad4d16fcf0f87fa846144ebe4d062133c3597915ff03d1434b6aaac50562efb0cc1ae4188a2499c47d10cc975bca200583593f971d485abd1e3abbd6b8a9950bb95b62ef2548832fbba59d0de956d4c4824b1097f61e894b9d1f1b71794b1fd441badae3fa7c9a64ef1cbe9273bbb16de278b6cc4b9c396c7b573a79a1750da7e764d9748a4a234d8409dc8c105caa0f776e2cba62b11eabab7272634f70a04b6b42beae366b793f3e1d29379ad1c3588df9b03d33c4a55c1eea86334ecc58bfb1fe8ab1c8266d8f4a4422324287def54c3ac3f856dfeffa212137c575b29d2ab1425152a352cfad7bfc191c62978e9987893b4a1c680c075d15896b79189c59d0ccf82e7d49241dc2e3fb3fa8160b025a076d6f92404f20f41f893482b3077dd9c196390f9be0389a758d1647dda6cbcb2363c1fe542858997101abcf4e9fc104c144b4f58d5c8fc51e17cab3e285680fdcb8af53614d0bead43d25c7d3a3662ae8ae484a3971db50aa2cd2e75c6e2253f7a3b78a5a3a0ff4173555611651be995b3cb2a212e8afc7963d9647e2b858050559d10ddf60f12f483812e4fc5e4affb55e213467ba1db4bfa94d63c4ca5c311f0e2fcacf69fcf502d99218a7a2b7001d5c0618b370ae5efa4a606624ff4ae0176398953f6ce5b493106304e3999198e7ecbf910ae5442090703e759a20450b4b7d32387b3c9c970bf765712538b7d60af992e2d5b748f4632f7929266c9ac66ad757f9904eafa355f67dda8a65f07b7c882ba1dbb146073bf6692ea4749000d4e9dddbe30600b6fa8d06e291a709aff7b47b64a8e22a073f26b5792aaf36d3e5ae175068439320a0201d384654ca69de10c5ce4dfe5f20859943cc1cb6e23e20e52e0d62e2b41d3a262fb10bd2aed0b29fb09431e1cf53754bbd834c88f0f4bed5d7fec7e8bd6218fc8d03a75bac70eb7c71bd0a822281c77aab4eabab0bb50e430e994340c80fcf105434d3033eba0ead07661259cdd76ee663aa98fbe27473e29a272cadefcf7a563198d951dafb2a88f2783a10c8009ceb94ae3135e6f5e650be6f48c1a902ff9b84f3cf61976f7f0b2d5577c4e5455c2f3ef8eb9406946a878729d2956dc9390ed608a33837291825beaef0f8a386f9b45a136f5ec11a5f86db6aaf428538013e687ddb665867ccdff9254adee3e88432b1a462da77ee7a13ea1ad5cedff6fe8ad32e9aa9734be5b50e3894aab33e9a638088fa330e6485f4dcfe9e590d645b5dd3b85e4f5bd5c88b5657a7be6b65805ce686dde2add6d107c64f98599f452070925f4b63d3dce897c31daf698dd75efee4e7e6df450b12a8e05bbd9cb19d9fa1b021160851ad0b8fc92202f3d471a583e26305ac09fce3c2e5102eec54bdf2c400961129286fc68e8f1d4e9bbedd7b4b894051aee43cf1a6d3113217cd0bb52164f05a809f94f84a1635e0930dbf06559626e8cb2bd16f97925c4c57faffa0a2a508fa30aff533ad8806aa31f5e6734abcaf8e68865af132a177f095ab015b07a0fce8f47a65d217db5f0c50c5dfe5a771d067a739eb50aa42c54abbb7ebdc05e08d0cc2735002ed998f7bf2e5d9f347ed0b88d9cca3a8ef56d7b66eb82b3ca12b0c77585cf525a65d9f6310126c9d2f14a1cec7d9208a8b84703d2d4e15acd8f6caf3a87cb16c00ad04d5e89e76a327ca6988569993db5152262089f3c5c4d86b2e6930e772386211b4ddc92a8973a332e14a1fb1ddf06cc234702f27ffa0c7ad421c36512a6e55cbd54fdd42af88eaea934f2d8f68b77b8dabbe6c4e5827d16706806504f1b054def8f25daf45b757ed89005b8357388ddefacabf152644599fe782e6e1b503047efaad203e35a1abf227b1d77c3427a8da7e0fff227239a96563e50c542bb60bbf6862cb1ede0dc1d443bd28a86368665f0335b7d6aa7652438b54e449e611b2e9670588baec3ed51de2c785055ad0a49aff7ac736f07db27a32171a84392d8f2b31712d69daf3f128c5154192b3f50190685870478a1286a26d664c468e1663cae9b7fa5cf8ad5131229c7670dff9e24e625cdc8c2d6cce397daf63f792f48a1c358c7443470bac9de1bad4624ca328f745b390aafad4ce8ad6d5b19d85d7549ff2ca51f8bcc6db0927c9eb8e5955548381f71bfcc867e6ff5e0300f3f5ea796524a6a67f44db3ea096775b1989ce9b7914af41dd3b490d77fce7791fe6d300a2c31a2699feee895000b99a8620c338c0d47dc34645c20c2640a8f47d783749c8dd4ad418afae5c2cf70cd832b1c85d462b29ef7e9ea3529b8acd871909d1f4099c38deb4b028ce049397f5bde37f008ddfc5e807586d9f74dc47ce4eb69523623950d54afa4a4bacf9c1dc61572cc74857cf329b31189ba559ee6551233caaf9a37c014786b84a9a9d3fc1173f37e2841c570d907d2da9b3aaa30e148fedbd00efe7b3b56433d88e043181500f69f4f2321bfdd95065247423136992a6b525ee8de6e08f0a70840691a1dee3b41e597fdec6e555bc0ffd54f7306a3704101941925faf31f975c5121ea2fbf174ca72f3d4ff41a9f35b1e3252f935e28914bf2dc2b28c4c763d4c1bd6da248cc5c89e4e8b2acf89ea717d6dbe4bb50da602cb74e4d2fbaf18f20aa1acc492fa9eb79fe84bbd7c924a77ac12470996c8d64cd2ad0906a5774c140eb3818ce4e4d99e85e8054b2cad6250e0fba0c57765289029e7bc6a97aec65361e19f870839222e6ad1fa7cae24cd4bb5c974fb81ac7329c9df368395e027daea589955edf6c9998f6162c3b1db84eef9f95996057905e64c8eac26013d528dcc4b08d66100ae48ff9d61722ec6a7e05cbc9c0d4bf24f4af5c238d655d4da6f53084bffd0bbf98999529bf923b4acbecab6411de3350b6eb80ef7ef653b10b6130302f6d56ea21c80bc5e566bd508bbd6da3c20892c1efa1261a3b80d191afafaf225e5f73120360152ebcee12e6a668cf9562002f711bf12d7f68a04e5ff04e266bf9d24fe176e9c34fa1da2191aaf34b1197974d386aae25de4b122477bbeeac8a9206953a4b9127a4d8955c8aed7ae5ffccfdeee740f9bb9bd3f439670111da767ce20d1adf57fe72db5f39bb668810da904482a185173101b8a3aada3de20d7113b899f303615f2b19badb4b33643293718a2b8630e3087910ec3a59bc91dad3844fdf1df040f74ad8f5d8ff1a6ae946bd6f3406635385cb460f09d37863fe44c6302c05b4aa78451105741b5d3e76677285612eefb455b64fa00349ed300ec078d58a53bbf5ae283294e95d0460058ee6093768648d02a3f19dab898c41e8794f840402dca98ad5c1ee862cf9e4c1b85f5af752a0220ff11d89e01cd0d4a7e2be4714fae4645bdd02aec239f841e033e0f0a7899cbdd146323028378c53d1f9b25bac83f6b4e70848b6f82bbd1a9c8a28530e8cb01c2486c98a4eccb40f554125656729665223654768b807836023b5d8e814caf57ca2728ac86e0a7ab0a21e4f40a5945716f4eff28cec20f5061f28f5bf67ec3e0e38ec80f0045776184e937950ed16ed88842db4b713feb70db686e2ac52fe5866f409f9873c34f0109de3f6f34a2f6d08c5583aa1f195d9de739f33144ba1dd7c8591ef1597529f23f3d96b15d0395760a599d47fd348c6450da3b9605432974ba8209517cca15863ca92f385e80fbff986bcfdee3ef69656c542fd7c82c16a33c0711a187bc1dac3c8a7f246bd717b271400eb65a5e1faa8dd3fa212193e7256f63b0558ece837a3b2d8094eab2174989d32465d614797a49e29b71237b3f14c22d824a197ab98c5b71ad3a76443c4d98872cf39fd72750dd761682439dad3d0516d5c9c81bafcf3ed12f4a23eec5f2c7c96c9c28100a1e99f197a33498380d6b640988b4dc2c3bf2945db7eb220671363b204abb8fe92b727dd9f0b399e77cfeef030cea125bd700118b9e9583ebc8bd2177709bbd58871c13e78124227e1b400a880f9faeb99bb02d21453cf828d8f499fefde04d73d61797413788c67451070208dfbe49cff79996c2255ada2cf90474d94f19d48a3deb3aaf8c0500a5c83dea294b01d8564491d1af08660081561da0296572a5e3ebbf165c982b4757e50d0956f39207759cb4a88b77d80654bed5f285da36f7eb697e7c204a5c51dc610645cb4c8c040aa0e2dd25e2a983be3ec66f1a2a5745cda3e5ae6baae694d67669c64e1349ba0265ed5ab9e6e11426e35c01e0337c968ff0c03bdcbe7e79ac17c028c086c3faf33ac2ceeb99e5937bbdbec8755b6ce431c694aea7e1f16f48f7f6dbef0593e77281939f2f3acba94bbc191033939d8b9e7b9894212b97bc0419ae4595c3cdcda5e68b89828c575ef0df343c024f499e39f84d98ad6ade44889567c94d3bbbe6db4559e000c510a1c4d8457261c3d5dd4d8a70f44ab9ffbb0cd6255f595d9f84d1e2ccfaa75c8b73e266c4242482fef7ab518435a22b85580688ee2e69a2563241d1fc492d9dae68b4c472fa703a80ac9411efdd504de5c4adeb51c4bd745ae63bb2963782da35f6e8c524d31f0f812f5df2b9b56cb7122b39139c3d56ca1f09a2c4fc51fdc6fe6a3a001be4a4f66255e0267d03d1c8a2272c48741a5db4a25fb7b6f4b0d65b8e2b8eea19a5e341e56448a851b2d0ac6db5bf4a8b906c6ec7e5352d1567447fe844f89b61b9776cca97e84de91afc2ef864ff29b919f37297c4dd439e304da39d4ff8173be50806e0fa52a4a7a89512bb03d0bd3be34e22202aac3221fed3ca9f084e515809d3594ac6484d2b2a989b90397f28641988c11a6ea5c2aca94b8dc64f377c183a0bf470c2247eeb2fda2d4530ba26172df760de392399cce22c1a3a7c724b54f9182859f425de71b7953781056e98ce67de13d60757576f41ab9cdc280e98da417ada8070c2cc1d49c19e202e7746af2aaf75ee3c1abab988149e9e68f1caa9c712bc66cc8c431860a87122ec657235d014d8f986cecbb8262742aa1cc446fe71f787caa07eb23d151bd0298fc205447457779c0a9b4a9e47ecf6eb465f7deaed00c83464facdfd48e5c6811c991b57207e2a1f9e6d678b4dfa910742597b1854c79329537237577395ca4e1860ec86f2023e9a764b02809594b2ac17b795aec82a378d57fffe33666e34b29ce6e7ceafdbef768f8fe8817870f57df62d2c0c9efb8f6e8b4e0027c518630828d1a14a75b471d4672eb79f768278c43b4b4ba1a8ecea49415492f2120ec62f920cc75a55a469fa60eb8e08b4ee9c6c0878c55c1a310245583e206f097454925416f65a20e6570f74ec811ab9a632cd45704af4fab340f92c553dd717b56bbe9b0f1d835bd32edcd87c329feacf0dad19b7f08a0b5806ddca208b95c50613f2485c92a12a7ed9c64f0ea311e7a2bf4765758f910a3f9feb97704e5693a4080a9f9b9c525d31e7350275e976fd3604e8ba65e9a67c4169f7b6b25fc6fc4ba0ea0c2ff76f24a40913f1dabee0d104824e67c14748db0b35b1618de9b1e252ee7ec2a8fbfa54854775eb0053acae63d30da70343882de400b29e3227827e8e7fad1e54d8e484c22d1669187ec1eeb32f1e3ba6fbb3fceba8990e1cac4b5c0eb3fb48ca477951ff65c12aad9861cec32493596121176294971aae8c501446d3b18c2a7845a3e4c598a36e13e2cf1e3889ce73109851fd3bd6c2851e520c31ac97ef6f12f382a5e5f94ce9fd2d15b918e659eb11ea825cdde3e87fd29358221deefb9223e26a53f9a50f4e3bc133547c2591de1ddd2cb03f109c85c414fe22428524e9a23c95f6eb185ea9641352e84420dd20001ff9be68f82befb5e7e04ef62e0f8570e6a5e4bffcfeb605d53f8445215308279162236fa15ebe79e2191c1d69830baed10a52ed2efcdc48c33093f2680d45ff5cf63d4ac5589bc65f89ae1e85b91f669f0ff700e577a66cdc4b68447010d4dea335ada02007458216ee55790a9601b6833b8d08e66a69933d85363a083a9b78a604c12f39e27ef67005cc80d4aad4ac173bab12acb89d5a305a519e636716943abfd7695d3cd9e5dcf9cd9e96e2769236f03931923226f4e769e38189810c1e8145e8626dbf56cd38d01ffd01505396966570fac67ccf375ce29fa7d0f5f4d92b74bba0d70c6a1516909403a224852e2953cc1ac7bac3b404d2e38ed94693817ba314ec6c26fe9b290742c4382f6b3150c1ec83cc069b22cf6acda3287fbf43d58a3304bfe0d5fa7b8d013ff1524eb8dd9982426347998f2124140d29a84223e90f9e7f663653982ba44cf368935577e006366666f4af1b14fb5c2e0902a10f3a20cf6b7c82dec76228f3aa6337a38f756ab886d32ebeafdae612beec69428c61863663ec41b9ec75cbbdb3f48bd470c42974a345dc8cc9982ceaa3c707f5db7f099ff84d83adaeb395443d080cb6c91c8bd047ee53ac0c131c5800c730752364e415de7f933ef2251df3e557a13610bdfbf7469eb509ea56de68cd15a4cf5651daacd541d5a51312ec69650b8a2ecbf39aea12da1699fa63dfed1a79b1d470eb47eb75b6130f0cf3bdc4ae97ec914a41dbf9d657b5b87aed2d9cf16f166b870278fd81b867d3acdc399c0a0686795f8bfb0bfcd54ca619fd1eac6f883072f03fb521be623769b2455c858d381d7f314d156201867c0986f9c37968750a88f676d2bea638a4068f2477c9f106dfdd7ca7db43ffceb1f288eca2c2e0c45eefb55e2d76d3e345734843f56e4eabeaee93b016767fa9c8acb4601f44a39691970890b628f0b805f3c0d8a337a23000be3227a627bc8e6837cf53ddb4aa4be6ebab388f10c7aae0b40e0009db4935ca4845fc90644872f647919150eb7d0e6581c910926b3b79c91bf402fd1d4ee1460307737adabeda816545278bb6917105baf062250c27c226df138992ea647d39c8a0d647d6fa31a9e97b003c4d0bbf39fd38a50cd3f364a97938b8f1fcee708382451f6280a53838ee2a6589476bf1b370401c897610318128c698c1ce3660ba8dcb44e8f44f070e536a57f06afcc403e85f34c3d30121d572fae36726628d54b60c2e02a380a1d5cd2ef4d440932c4a2bd46986c60a904312de72a38fc0c7d8762f356d337c1865e9661c01dd2236fc964df923cdec94bb7a73729986905a52d24358d8e835efb3e7d8edea5accbf940b02fea3d7378c203eeb2a23ed53c5224eb7ee00fc0fcc982814dac0815e55f4a7a294591a70699ef32d05124fa474c9e38a5318e7d46dd451fb0836b32152c353d70190e4495c6fe7ccb9b38acb5511dbacd0b5761ee3f23c3d703c922e1469637a5b44e21d02dee67b77a82ab0448b64d7de6c69c2feacf666c5e462c4b7002ddd63d2e97403f3c11aac5028e95d59ce2d830b98dc7b86573b7d73d15eb78508d425a4c296a8dc81a7cf0d94941a5117d2246121332bdef089d2b3c56ebf9280f18828c9d009a2f4e81ace7132bb0df9787895162ee8b5e756b0934863e7aadacb16032c535e6413a8757a01dc9ddc959f4560e538b1ddedd72dda2a0b72f5a56110e9bba6e8dfc56e3f239e4619e4a5625eb893e507ae02554e718e07ccf283e500b356a70b0488c8ecfef5eb3c8ed9be91eec453bbd77f5121d749bb5c9fe01fb976465a1399fe72feb40b7cf705007e4151df568f13658d2e82aa951b70310ec632ec0514119dc51b1c64b9b7d9da2fcc3fe3e37380389555708d864656cb612562a87c8d8153b0a2ee7267106b37b08ea1edea78c9001bbc9cc2f4b4e9802ed04d2d2d1e8fa2d2046de11c756a79a0a3ee856bf4b8034526ae441a50c3a1413122b591bd6f9ce3a6fcf9e4c505d08e2ba482a7a188541e465816a602b67240a37900810f7952bac0efb8f09853e6cb2ef48390f0af8afd835b7362e32d68031aeef38f884904e81130697a98ae3e995786237934a5568292df8d035b8e23529bf32cb58d3e2c97d4987f285c4f48f306d73106bba4215665f38cba7d08f057f5eb1e7a3e453d34396ad78153795d695509e0fec2c54a80772a55eaafd3e30c04db38927e9c2cf3fd944d65e28fc21e8ef5f0bbbb6829613418df114ffc0b0d5a49086c685419659ba358df5fe3712fe1e803d4f0efd0b34d0c7f01d2bc2852b232188e07c6f4a996d529bc3289433694ffcd89ab6ea6128e5bba681cde367b19bc8b27eefbb6740dd8fc4b81f8a50395870dcc3c700adfe2d454ccad159117b6f3e9179917286580a2c317b75cd98e19325801da210bc9ca196c77be3d21b5504b8ad062ccd7977cc8e648cc203032b27f017d9ee4478a9e74f2c6fe13d42793cecb637d482a46740e1a40021e7edcfdb6e5ab279781c3d64c1843e452754518257cd3c33c38122025f61471b488162d1afe20e76d6cd0bc4003a744b4dfd3ef87a61df5b20f5b89b1bdddc12110c134a8ae8de2e613d9ff45c7bc7955ce89d1b38a307fb68abbe6b1999cb794505eac485db889f3dc258313fc197529dafabcd028a7eb7d5f83486cca36e75c841739fb5c9a742cd57a918d81f1159860637fed09366e54687392091c0f91ce90b421cf59a9da8a47ae04d35ca1d166624e93191e85fcd1e1a14845733bb00267246e6eea88e72ef4ff35cba5b540854138a920c9b8907862324b6a9a9405635cc809d4ce12fb678271abc124a3531b025680258ce667601018a83ca46022d7c381e9eaa4b7a303a12bf99cac2c7ad3f347316acc3bea45da2cff6ec05036cf4d051cd0866d5b854be0937b07c207a62640e8f60b28cfdf72def70dccf025f86930258865d94f064d3dc5aeb80fdb82c6f29dd078fb8effbd0f588e5d5fccfce46097ddc9f9b54c6b0f18b608baa50c0a29401fac60d67e4df9e8e8428aefca5e9d2cc29990aa3a7b58c2104823a1f91473807232369c300eab5b359b90efbffe4d32a37ff40ef9d5b204abcb5994e80f87e122efc6631964e46b5375d1e73bff37a38031a56a49c0ff24e76a721ed65d85ba69f659c38607f2b3146eb2fa3c5aa3367e3fc6cfcee44fb799a0836e98f09cb231174885af6e64196c3709f8ba306ce00799aacd22c6471b39599ed6710900920cfe84bf6b43ac4168a0aa43a0e7ec779624dd7f4c34f8aa9f29841d894f30c468d2020a4e7cb92d0d93cfd6c0780be6a80996349301d776f4f7e82e5a80e0ed0c709cc85fed934eeb1791dd56503e6f1e7e661e9442a217bd8ece593fdcd5cddc3c8a8be488f67c62940fdc7e6b7e37455cc24e09a6b2bf1c118749ed527430ca332f41781663704ceb32b64259bd49f35c827866570c5a45ed3b7ea0002d3269a1e546d2291dc9654d2d866e2d833b78b2c807c6acd7b29bd0e2699ee355b6fe5e02998e27c57a40ef28dcb85342d62584a3f8af469953cadff009ff1eb6ecf0c56cbcbb7bf4fa547dfec00a0737b9b307d7e06b58c55ef6480dea80c5a195996bd178078f91aee7f346518213bce8d2eb19a345324ef61721c7fbb8460de5b3591c9cfbbceb8c9eb4e3cda9eb96185187c016d588389540cfbfc2e815ddf6d3e953514556f6f3755e26c5b2e820aa58fb2985cc08b679fc619d7f99c22c29ee0018717f7afdfeca31e3d4e3885b1b284f3f2b5103bc7be54ac44b83508659c816b44e55f3ae11158e3851824b71add699cc6d95e6a1478bfee9f5cbed78769699f61c98a4abe06893d7127fee3f4e4029b63db7295f89f1ae05d9ed99b5a8c7dc5a03967a114724db346707ca3d166b558b346a355ad878db17de4a9a6bcec2460cb0d618a49956d6f34ec6b85299744a25c2f11cd2fe728523e45bfdb35a9b742d6ba37fe2eee49e88f01d90c31110bbcaa3e56aa2555e39d0e37973181bb4320dcf097fa9d7ddc3b9ca311dc3db39c2cafb6cacadd39e98f082af28f2c4fadc1ea6b749384a7d6262927a48e5acb8f4fd6844052142ab9d0836492701c2cd24c3f0c00474f30e5a2bd3f96ac2eed416c537c104b8bd7b9b97e6d6ca45758af8488c6771417b442bff3551a71c63d321c448f83efee7db704adcac2ab37aaa2e63f6aa0bd870fcc4ec8ba901d1fafab591e000733d49a5bb598a763a863b0e9570225bfc07a286b1feb1e8ab6363d202fcfd802636209cd70a4da7883a85ada7e5d19c5aed7081d7ebdd203949284560ecd577151e687048115aa6f6707b773b492262a20918ed7905385691615e69776d6c81f89ee57f5cf293a743f47fd9b8c59cb45d7e7144dfdc44e71176dc04ed21fab8f1c2573dd45940a51023fb0fd65415c25f5c8d73a9e7481d94eb48cf86df503bf599bd693d288819da1b4cf48c9efdc5e036971e35aec72bccbe828e35cee77be1d2b39889ac9add085fc2a2b4023b66ecd6699a662f05bfe6b59e16c04417cfb868b1370b4d8ea7bcf10ef610b1ece6d71e59508c22b0a4edc7a91629fd527d132d1f379a83399d048caeb2f3b33f89a4983637baa354ef0c0267a14d94426377a3f6e5f11cb1fe056aaa5b286c0a523ff03dc6514800b9dd610ee5fb1d1eef2952b0a00ebde8ef6fa8e83d7de3a244cd30596023dee757f7e77c5d2938107a4ea01269d3dc3243dd902eded299d09ad5fb16cd58e18179bcce44df5bf4261d88ffd00089dfeedf7e481525dd15638280dc172c9cb432c0d411e6a7f4e7a5a5ed57ed3ce05a5cdfbb9dac13e91c8db7883e51eae0e8bad949bf4d5d3b004a2203bf8d6ccec4127302886d9ab0c684171e8fe6876f0c853d6b8663bc44645eccc68b1c688ba4c43b984ae39093f6eeae05fc25f5c49b5a144c45848b9bf8966ed2b1ab24ccc387dcf9dba968eb3bb3d715113e5c907c5833de2f47e7dbdaa86442c81970644dbfa853c7bb389a400a366ce7d7dacf740b3fafaaf3b06a034c60de9c69e32ae76afd07b5fd36a7d65a8b38788d2113a0ed4867100c3366048075035d58692926ab3893517eb285c8dcaa8ea8cb4e406504dbea54ca2df1959d6f95020e50f2157451cfbf9b0a91545a2c9b78ca9fb5bc801baabf8db6d1de43e6a5c3f4fc53281fb4508f79a1a1b73b45d160ca66649dcb20f32f2f08de7ba3bffcd8539d8eb61b3a08257415fe0b63b6d84d25340fa6442aa719c4e441e76317768b8ae32c5a5f7fe9ec68a8b8978f11056c0d8196e05cafeea4ae431ce6a45d2bbc109adbd455e44bf9b8eb366c606949c720fb96bcbc2d98b730200afb9b241763e9c9b3c402535f0ec3535bc5975a6920407ca92e3f37e4b2e1f79cdeed71079dd910aac1073d39289178ee0cb6c79f90a588a396bf3d09f296b5e4b11b11532f342776ded22af0fde75b006085a3d0ac88c5b8f0b29534a94eb49d088fb165b283fdde44201be698e9a11621982ee2a8868a39dcb3b53f5a9cba38e5d1a3db1e04573f47ed88bafb6b77a666ff674af8621af5cfe2eecf8a211be8d62a2261e5b365be34e073a11ecc084eeb932802a65634b370e6d7b84b70dee6a328cd902c09878d971c83cc632857ebca13b50c5768288e17be2f5a0d9637a9ddd52cd3d9d6a047d247bd181df79a2b56e026f3b881e70df7b21b7bfe4bc3c9c0cfe45d07418e6dfcd977b9b5bfa81932f9fbe268e29715b95833366a023d06043b0f863403bd66b65f174c95f8bcbe18d9443ac313f23936569f917d91b8754a9743bb9e978d4bbe04cfca4e953601fefcd306a195e1df5c6316b593366e8188a1ca45d2f0d98c8dfe726a3cfcf31255244506555e1d439ebbae71ab62849a3c8fc754aa840f5b6c60014aa5910234894bdf39e56f5faf3416434092d0f5670a45093d5f707db7668ae0dc7a7cb83d303985184271e6e3a75322e9175f49bb072c8410db36c5e356105b82dc69532ada5167a642e08e6297f6c43f2ec041e334413d9487e5441dbe0774aeef5458cb6cde1a5de656bace3d8861d4d671eacc31dbf6d8ec39b3c63d03a3c0fc984154a4e08a581948f3bea8551babb526fd3b995774143d7fe420cf783f22a1ca6bb1fb39fe46e9ea0f244d4b56c209b4ba0cdcedfdefb6d51c5b82c571bd63b357a5b35590869b4d01ef259566ff1b773b3b6ee1ccf3594ddd1f57613d57507dbd343203d07da1a25f5211334ecd02f83aec5b25ddd4bd622e44ac496b554a8bf1bf7dd3d2f78a8adfa377759fc8cef566380cf4aeda170357ea21be30f82f48572b447d919cb3d4b4ecc4eb975ce2e358990752d3cbfd1ab75bc7c97fdf4d436bc9c9ca4d47743416801150b344a7b9ace9e5895c0d681512a471acc1763282b7dbe6b5bb6fbddcb83630e5d10eb5ebb8f453060638e5f776b392147b5fdaaba05863a49a80ce520525cf5dd6a0d21ed06ed6a0d834bd4fcee35a5f6798c0af2c67132bf1d3b8fc3cf4d77f647206c90ff0dc15eac26e82a448ef70987df7c16c906f41bfd6a03c6a93b973a20500e0da6ee19374ab505f60c92c942b33bec528b51dabd1917d5d4e8ed95f60975321e76077b37351adcb2828df009f7f8abc09779be53c6909f8afdd7b7ad57e28016aa0d8e591b436b52e6d225e1f45e881e966f4163eba984aed69dfa7bd4ff823dd0d70b6c716c6e131fd3ab0c0793c3aa6812d4c188a9fdc1c6bedf70d31fd5c52992fe9341653ba9133381834df54fcd48b091dd0bbb23ed76caa35614a55fcc617f42ec6558ed1d48730fc5d0f315713cb0ed0d826c2b4acb408fa206e828a8baa0accbd183445b101b1a2c8e0853f5b0d61f5dc60d942661fc29e621232cffb565576e63056048e2a9896cb99a0963e594dfd9905f65b71062ec6cbd6be0a9b1e6e39d45bb54754c5fbd6c462fa8b467b987b3d99f2f7bb00e19d6c7f281594ed0aaa1b0b3bd80e6ca85d9d4f32de266123d65457c3d6508c6f56dfbf277b6741388293d6b9680671e0df39dff6395bc8d49bf9f5f7d7d7c86150fc4788a3319e74eb101b11f66507b40966989c4cd7875bec41f7f427fb88c5200c0c1352bd80848331de15a534d20e2f2db644aad7b4ec4d71c0520a8aadacc7ab3641fc0ff194feedbdaf12c5476b94c3feeff3798013ac551d73a4fbcbcc070cf2a2eb482af62724359e52dbc09a616f6e166658d57293a18ac52b1ed291e49e088d9d033960fb64e2615135b157e84a6c5325310a33cd9804f058ae2910b7959a33a4c77226d84aeee39b8c0fe457cee143f4bfd908b0d088ecbe36c5e48b678ef0278072bb401483ea3ff07dc91d0adfdcb6bc467ba657f7d8ccd0c01f8d102652d31c375acff909c8913bb1e525ac119a823f2e128c925a1d1004fffb28d9074e8425b2d63f1f61715479534bc96cd8f593fc1e7da1336d94c254543ed58be41c7105f49130c02946c842c2aa2c5ff31161e9cf41e14d347bce281272793a8bdc534612f35db5e3e162c2d2f123af6e11be672c2f12548db64f59378e5402adafd1af0392e76f6eeb613a6567e7432264632b6d980171026f9462b9617348d06dfcec4ee8423204d92c1abd6d8d4d372729ac5361a09335f270644c80ce086cf5f3e0f3009c2e773da9e6ecd82ce6507c1d1f87f4a2c3d500ac8873c85684b47cc2fe2dde0257b53aa86f4c4c6eeed13f97f89be7e8b9ce9c838e460a617f5ff9d3cf19e28f62fcb9d4dae10a675555c315af8f5ba5662bf5f43abeaeec67238aac5676b8abae747dc6e61878dbdc1cb73ec10a1d7200a786c435f344748bc83ba40e5563eb455e32b0d786af908a876e80871315adca4f04db403f960d71b23ac1b72c375d31da61c6623ed42cc07414b0ca239895c7b2220c04d3b46822063cab4596b88f87aa01a3eb48ef3d24dd8b8972d678dab469e733305cd351459af7092a1a3b444c6bbe17833dd96ce89f5b802233ed1901b4e2e8d080496b2f0a745802128e31117f42917adac390c170a127d50add30100011fad693b636e80c1ce7f0e01eb291815ca27d98b6671c8a2b61a6c6c54d2562b95ad37c07ac397d7b9a9fd6e21ce1e4fcf31ceb285a278f6911137599e2bc3c59f9c42564498bee1343bd4e98c0b2c1b3e99d2219a457e8e2b610cea83c65b806adf5bb2f888a0d3269ec52da1cf8d9c2a2702dd2a99fbf89e60ca9312575158513640b7e1e8de639fb59f37374e441a2fe8a4e6ef30ed09194c832142f872f7d0be911e2ceff2da94a1d005a87a03a43e4c37e3503fe309758a90518a70ea448f07be3a57e0f4372374e27ef2c2570bace5f20f481de242a4af0db80d8eedba9f41cec1a8d756fe8a0fa149d1d825b5ed5c544e17fd90aedb8d819bdcb955350409cde9c1f50eebd366b92c1266af28c31ed63dae31341c7441ebc2e60ae8e5a8a00da7103709a2de4608f296c8533511d567948ab2153f5d6270387ca23010d92cbbc82e77b310aae69f50dafdafa5827faae6521b7d1fbec63860bb8c85137e144507f2e64adac3b9bd104807be3a2385dd5f9b4721d1f11361a482291772452ab72d7694d5e846fecb5293806bbc36821fe90703f68ca71eb903308e75b696b478106c5d02a2508af4408db443cb6043939ebd3ce17283db1107ffe3702674986873bffb26546978c1973c57c31d69e87f7710d28fee404089aecca354c2d8a44cd7200632907686fc91c264acaf56d17b6d4e67bc49839209fea108576742909a2119068e9657d2e368d229036481e5ad0172075b99844746d31aa97def1a47342829253dc6c8a0bf55155bfd44587b95c5e6e2a20cc4cf76d690f875426433ff5ad3e4b2bd8491c1da3809e042ac1b30bfed3de474485ac8758d2159ac4e2c67c5ff2bb29610b95656a395b339cafa9487a7667c123b807014e3585718a2c150e70dbca72e569bc2f7dc0501a6d79428856aea39f5ef5ad1c928e211347e573d325f4f79c73c13f5438bc9167501e118a48ba80ec15b85858795180322706855df9071c35c096837cf496b6d8727870664fce9345baed1021d76397fde343493f8ea36a79a14c2efc51ce4eeee178bd505ee040cd22e326ebf59c5dd532e61f5a519b6751c1deee1774acbbf8972bc62fce0684551cf005cd587e0034f540deafad1152ba1a551a837364cb735156d0a03ed1450c96a7016f3994d4b19ba2b053ce94e35b16fef9c0b219be3b65e7d022876e249fdd16bd075c95169135c7388989b872edc27d7f2166bdb67d0d0111d88441df139a314c9c5df4408c77907e2ad2b5b380ebbadd56b396f6160b6c434680430e58c7bb2300be8cbc9d2c55a6ce3c4cda07424604e9d5febace47e97613fed378772d98f8b1113d4972b25d7251c439764bbe22b9ca94307520a0c040452096b4cb39b2c40b4d99e29891f853ff0ec087847bd17bcdcf05d3cc554d17af7a98bc9dd0f6cbf074b99fc305b5a00c906706ed33661239d8b5dd4f20313b1b41865783464e9938f82ac018a2e58e05b90ea5ab66a152076997e500f6e6a1aefbdda2499c49e8f06630caafa236a2dfe50cb613fdc95d359e1dc31ebca5d6c1fc913deeeeb1248dd4617cf3563fbb2422363466244beb8677b0cce73510b8aca904dabbc09b1b5493ee1af223312cfca7c8fee9ed942fdc91e06d28ba66fce4e25826a4319e530f2d169aee8d86556c59087532b850a910da4d654ca689823c4fa3f1452d8da1ac3ba6eb828432d1483a2b26d9e243d716e7d0472aed663b5885e77d86b218ccd41e5050068be1a98461d3ec0cfc9dc8270ca5e31ee7e3a4dc556a86afd73e756cdc5062e9e911f3e398553223a899b6e2732eea03f962cb02eb92344ed8473755dc389b56f7d7095fe1aff8caa83f3113eb7efccf4c55e6c520095d1c65fe2bf330777410c6418ab2efe87cf8c14fa1b6e5e8be04374e46a98a73002613ef46d9e3cc9571ec812119c3b50c15f25cac8d352e07d144853d7511e685e7303ab3f2c9030ae30f6a06440c606a7c333d8041dd7524c82c322a7a21acd1b64f461620c62c975b42566286d18e8ab44ed9b25f3f2fdc41fb97dd806f1bd7176c9631588d9f1d9b2586e9180dfb9a93e9715346b3311bff2676aeceab9ceab908871ec04d0f0f6d38592f7934ce9b229b688cd4c16c91267def98eee38d8689d59709d2bf06bb04a287a1fcaf3e354adf49ca5dbbc0ee2e846632643678c621c74e6de408cd6d4842cc1927ffe04786c65151b2622d129cabbd2d704d265ab9df05707d54d5087addd2539c49856199f2cc72fdc9e42a73ad81558da561adc8899ea1717bd54a22da2a7f0c36cdd8e4e49888e91643dc0d9090cb9981b9d6cca9f04a7eae3302c16a38bada1422253e873bc9d169bc73ef0ffecabb3505b5febe666031d91cd0f631667789d777120b9564bcb40badb38910a9dfbb299e71d0a8759f72c1d0f10c2886df5df4ec3e1a3eb9a44fa98076c83773ddbf8a13072183ec7b4d169120c9273fabfebb2e106277bbb0414695498047875a0ce30c96575d3f113bd41df5c3cb65824f2901154d596b2c4cf64e27b5abd1b3bdd23b29e4ef83026cfc060c59df34eec318cf80a016a7abcffc63bcaf503161ae21418eeac99afc81d5d51e945b80b481dba7a901cc069c53d4ff11cffc97f4c8268806a469b0b4f53eee25ac9a152a78a28e0eb64cc0feb07dcafd718e70619fe66cdbbbee9a3c7013cbd2e2b0f3ee83fceec9fe3cc0d859b3dc0995d9438edd7da0022bf03a093076e7e3a8cc036a8974de861ee656d080a61dfe350982a637705857ca52fc5a96baa718636b98a28d11177513a2b04b0b57266b0c1e9091495100dc587a812b149187da6a61cdefd51f8c06254e0a8314fa3af7bea163a7a4ee2004fc384caeef1ff617b4e41c74990d839cdfa37b35bcdcfa7b2b8073f4826e55f8c784877e99134f9b5c7cca553756abe25d4b193299f90b5cc9b3c0dddaf292dd2586c199c22c39fd6ae994ff583f564ec8c5eab8f7cba000470d75f4b6834b12eb562f330abfec8ad5f509d3b052d561de1d0a1b43709d96257e330a0ca0260a188254e848155ca806ae06385318fcc91f9bd0461d6df7e358acb649a0d2b12eead320bbdbc7ab5f9897dcb6a2d96da073b4d1c76cc1aeacb299b223e47d47b4552fd24c039bafcc6f96054c36a15b082bc823b36dbf603663fa7504c888ede8c24ac67630d41731b2661e6a57e492d70361cc494f025c6b5d7ebcbfc27b818b6ec52fe5a8fd9f6828aaf17111bec9939b04b33c7750d2b02bb7753558f0cd4145d7ba5bd61c62bf521b5b2f8fd3f99ae2b901b559e263ca42f0d75daa2d5a7eefd0613296b39d7ad2f12f72fd9b3ff5cd71c3c9a14765c78056d15c25165811989b633f959ba32e0cb07ceedf92f0636c150558eb343f845229df122bfff99cddb3fe2dfd7ff62356bf9e537513aacb7afea23a13f2ed32060b275fc82d44968e3673ea9c249f309c0b62aa6aeab9a1d64cc7ffb42e0a1ac0926b713d4349e3d7ab717a9249cf04feb7cfb9fb2b8d9c4ff9603db5d7447d1d71bfd05a51c8120ae21775aa29ee17d2a57529f715a62e675e4a71d0dedc49f89a4ec7e2870d6a7420aaeb7a200d68a2cf595f030fa333ad4dcf0771191ac99965b71da022ad73c92c609b6f3e037359caf49adcb6ea71fc46a0237cd7f99d394f763c319cf997f03822397aee69cce8849636b92c2eb53f73a901fbfcba06a690dec1a429b75cf94874b3204cb88c968ebfc7e07d730df76840d833c56ea6d629a4ad0947bb93b95a89cb1d1b1fb1d0a272d495900e8b111b3608086cb652d16315897184a8fc5fb6f98e2fadaefa110fb9ad36082d4b225828af0188d6ae63c4144251c1b719304085daed5cd97c36a23bf15f3b8aed2072e145cc5e3b3da9c8db832204ebbe1c3f6c2656690b73608e6ad0feff3ccb2156171bf0adef48cde7c264a2d32f7f4b97eb86546bba24d446adc663d10a17e34067a243ab19ae348ccb1257962a3013c0fde8d8eb417943f5ff083d04dcc97c0fb1979e6da515e738f7f0d53913d56ed735ae33d18914163a30da24315f8851ace530350eb993fb79bd348d2d75696cee3c991e2af94103e2095bd854dc04cf98afa53fec49860f1208c1beef25731d2555e979bd70355dbcba320d05c7e95bd49179d9d80d6080ac7bb6a1972ebef0663bc548a348ce0efa0aa17b0a4a7214127d6a3fa4b45f297aed4de288cf1db08a6b11247f489700a32cf3a4707a97d74efc618f841792d43872f539a712f416f8e9ac599c8f2b811d96364289e464ccf1a535ce7ee841c11a5cfcc243a433f6af27007a9c2fd099de648be57064cefb48ccf3aaf50304a3528d0d464044d707ae2d3907ef7af22fda5aa441d550e3de27abfcc471d92499f3ff770cedc11228d54dbec44b6c2c579e2e5d43abecdc6521de73c5efae330b98bccc152401e52a4c6665ebe46beca56e0bd4e013fe0f355f800e56d7371c7f6c6b05d2a9143c1c1ff94e1724502253761af7cf0a3fe2d98a617c848367765a1b24b95bbc48b68a2ca4e079c9d11c0730f65bdcbb67eb239a01cf1fbfbfca5b7eb7811959761322e8790c5636a8245b87d702fbbe3d328ea43fecb7e97032b14a803906320790358363b05d47329e8c32df71a7ccc14e7bf3e8287376f0fa679f6f4b386f6bc7d93cf603dfc4c924589a57be1b03588f48f4f10ea52c7b24878abce7f631ddd298cc6fa2192a9bba055eabe7def97f2e84e5ef267ff797c1d3bde7edebea101b59d7d00e955cfd924fc0d4ce5df8c3a85b5180fb8d517f73b0883a786855389b388dbac53fc09b2124dcb71cc9a010f03169ce1f86c8e73380602b69cffed787525cc8baa6039aedc3cc560c2b74b894861b31f2e945b2723d6a9ffa4efac218f7e375ea6a115e4d101cc45a27f8d0fac900c06acf7fe2414c17701fc4fcd65d8e4d252f777c40b2af0946cb093ddb8e8db35cba9a97b393d365a43de3c03443088968f724bb00b613671960757854cc8a9abba2eebfc2dd6a7832a3b3c8ee2d38f76adee58a07c7d6750769fb0bbd3c7986fe81e94cdc869b10b823d27cad2b2037cab8d50371148a9016373711b1b0f41c68e5525f31abb3f2b793034cd408bc281538d91d4acbf484b08b5e6fdcebe8090e5e4d6f5835f7656c85151e3bf3103a15795f5ccb8662812380bdbf7716390586e8670b920be017d8baff89b25c11058ea8d130e2a2f3f909f8303444ce7d0b9912a0424c4a474a4c134f410243daef653de20dcd7076be0d1f50c93de27004e4e52af4479a362a3fa1572840224fd6442be2a14491be9d7103c6363fbb1f61b691129915b17f58e6a3a8d229406e98af7b1119af4e0f3ca4dab2dde28c235b68bfc9eaa89d9f4ab2a5100875b989dfe4900ba7799cee317ed4643967c44b88f4ecbdfdab83b566d951c8c9091f881f933a0eca162fd57cb9e9e1c89fe48cd93fbc6c927f0f985025a9cfb4a0417a60d86594eb0d57f00d0af9cb38d67ae45d703f28e633c33dcaf7deb6ac3eee788c7684888bbc4e8f3f7fb3d180d424907b1b145749baf8587db87bca0f40634e49305e27c07e9624cac9a159fd38a415c3d3418b351bde41fcc63ec2b7ac58ab288d7bc811fcf6b3657481d4c481fccc897f06ded4cb89c483303691ca8e6023221966ebe848b4bda36af7ca7daba4efef726be24ae65e0a98d87eb8cb072993d9332e3187873bc6bdf3cbb08e77f386c316af806e8f9c9c3eaf5a0550caab2a8b87847cd2dbbbd3318abdb2f014111e2a21f6adddc0a1fe1df834d38d22a60f4dc0eb365f24f9a92f143a6606d9ee7c481d032aa2db48f2a9b1ebd36e215d17f2a9b34792ab7b3afc708e078dc0da9bc311c1f27d932accd3adc8a0b3fdcf4d099e26041eaf4d38e54b4153080c857de4fe90f35a0ad7ff6a44bd0dd197c38e22be2b051e5efd37a9f3bad8dc69c607034810c044cf1a0592439d018bdae1159033dda64b42082d1d2483bb716c9fca27cd86536cb64d4769f9df3628bb142344ce2965cb6245b2241cb3196a5ad76a96281b5e8ebd048ce052a773d6630f1b4d538331c02bf22559627137a87414c1df547742c355b20b4c61d92c96149ffd95ebbfd8406decd99255dea8fc320371ba7511ca8754dd5bfc71fb31b25988678924704b6092cf43f5e9e34c132e592a0e3e40b53848c208139102d33b9cfb19107d51b313dc3ab1602e5541b429346d98c2cb6596a11014a75ec431967214152415c27b5977e07c3bb99ac8a60db2dcd40e2139c492024f9d786cead47e3b82b3149490143bc60d10427345a49a71ab5052cb67e0c11d682735366f25720b7529437f1f9c151a78639389571afaa4e5df960bfc6dfc987ef659b674d020c388b40107a597d4ed4dc80502e772ca8e4477d2ea6d76ff9b0673ed0cfd3397490aa4f650e1e231f29fac8bfa7fe210cdb708322d58fbcfb20640a21deafafe7db9723bfbda200864852773f9802ff8f3bce605ab3c1a7485b51a31958ddb28109834156986ab76134a1a22bec0fb7744d2f82f66165f8f6b109a4f6c673ebb1a3698537d2090c4b9bc6532a8154729e61896d2824eb634ba7360b7cddcc3117dca7c8121849d7229b684186fe67e348897bbdbd312898f03208f52e56507a0b63c9522ed8ced5978335a0441347773e845a734baacaca62e782e4d565f3b57d9d89d05f98b185e05dc69a2dc982297ada453298c177142b616993bdf3181d2398197d36cc6c3bc7c48ffb4fff7f31319e8bf2a730d89d6d4b37238ce28316a54f267b93a029f35950e6be805bff0a1af35b69efa335fe78ad12464c9f64b93765c6620b86be60fd57d75250c48d4223d67ed3e22031fe8662d899bf8bc41b5d0688523a0bfe34f17369494ad651c5094d3d095ac7d5be5a0ed64a330c927317692c0f2457bc54796b9ac7ab03e8442ee9e596363ee9503e1749e658cfdb9ccb0171f62b3975089016fa26c6b213541636c7d0ac610c43f0eee63bfd4f48437f6cef516590549bc9279c5ade9e592f6e03cf13665315631fc5e9e8a6e5a67dab187fed82495245ac07d4a1e7ade5b4f0973f9f932e77dc2da8f496b93310639597d9078ba7b3bdeaa63c14a1fd7ba27d64228a528895c2c3dca582240ac6081d17e16fc0efbd10763d353d8d179e27fbcc9474aea85e800af39ab09851600cf26e776e52a62c26b8379e34ba26fa5564e714981d2603e097fe1b91f921560f1485b4146535e2dc421315fa1a3abcdf3cf580a3253b2d179afade44dbcd31152f0888226084c1c8bb5cdab43bbbc8ead03e0477ef2d722f6315a9fbd8ab2460d704150080fd9dfa9f18b09a04a025b3eff13dcb5fac10d66d9c185c52475b01b68f1da13c463ab014ae12effeb815c75c6ee200ee740db44a7a370495527850dd6b57de93ffdae692ce6f7b8745d009a692d54d00e8c21121d9cf9657a8fd503ad824adbb79a52821ab7d1d362596cad6f1255582640ea237df3d0dd05072ac892ca1f20aefe3060d525125f016da22baf6019bc0bf60b30bece83065948413a08696a82381db73ae3fbbf945ea60c6ee98cd8242c633c338986104ecf06b0560750323e7ea1450509576fe632b8be557befeb24f680952258dac2519169e9b6699c97c95d33fe6026cb3761e170c96309635ed64a55835deec9d64c278ebd0bc409f7bed7e7c3321e2dc30baf771680537d892b9340cc2f3fd5debca8311c4cfb7f46b41605a21dd7d70187a665721796753804d9c08a03f2b04073766278b6c342d9ea02bd02af5cb1dad2dc02f2760f116dea8a5c480fc9bbd656b1dfe154a5a980008d4709d7fee5a0dc33749b79c9e6e5c1c0bbced040c3cfbaecf2e239ce78d31569afbd9e301ea109bf6e7cc0c2ab993db2de5d6719288cb5292c8649005f5ed0d6b71c3cd95c9162bca79497a8e0bcf50571185e84de432820f8f455ba4789719f97f517ea3f7c406470d06df633b25808b58e136f73aa6e013fd35c89003fef2063f0a7c4c2b2b1439ac451b8b85cc7004731d4e1328c83594184d783be29f29c2562951911d6408e5969d0f620c4fb77a8af41f360cb7fdb9f3228752327cec04fa094a601c88aa9387e91b62440d8d5f93e27c53231c12429ea13f6c6fbab4d4f9a206a776d710c78e6e5da05db7ddd8c990fd5b7764dcf64defbf3063b85aa0a5d3ddb2936ad2fbcfa5ab7b45f075a8927a3ae53d584cccd4ef7b4b7e50d4bb61ea7da9478a9bc32439743ddb6f35b194396588b5c9c980903084809e68e3843d2f988891ed3d716cf94656fe1fd431dd55f9840788a0d46a3610d7a092bcd8e336aba8953b8a2b9dcc2ba1269cf3fe912c30eae8f7887d4b5338cdc20a47d2294f4d949d7f378b0d5db8d6a343c1785ecdc8fc5da7d57878930c447ec91a987e09cbf342dfe3d0059fd2927e7c7769fb87e668125add689793f63563851d80ebb177d007f23bf60bfddd64830b7e5f57a322608d3a8961f53fdc90c4d4624493b95387c5de8c17fbccd39ca6dc15c1f13f7e87f46497be8dc966d3961f65e359150ed89ed9ee66ea431affc3bb77867576b38cc522ce5b3b1b619625c931e1151845082af32c1580cd47bce6182f3713a27162bdf148652a94c72e070f86cb829ac3e913eabe3557712acc01de664197c8926e9aa903235a85d2861731a254cec053d0d6fec300fcd8e31e5b87f78abade93e6bfa6daebf7c91eed43d8616fa919c491bff11e01a166c301baa1abbf7858067c3597d98f907129e9096ce1e2d77e6581523d103f2ab0698f7910fbb9bf3a9bb631dd815c1f5519b9cd94d3bbc85f89a05c93516ee9d0747f1fa00861a72d9de91751941bd47e71988ba5febefaa3634f33c96e4197309fe18ad3280c3db678b337eb117867a50942cc189376c4d515fe61a7dbea5e5288d2de591636502b7e34b067d690c48a6971755e5cd2252d012a8379afe220f3fa31808e0e87343e3501f93d3a994326c99415d17020b2c512e1c67338cd42607ee7a63b7db590e1cb93d64389fd357798501baf617b9c475dfd81107e121ca380fa3b552b25b7eee325268cbb859d051b14d1fe559b59a6f04439a2152bd4066f53abe4720d474e553bdf42d937b595ab7fd35ea7c760e078cdfb60f8235d80bad3be38d89176de6d288ef8d364812ca20c64d54a294f9c67a230b3e9e9f507c5253a057ef2a0a93347b243a784d1415f76518233f57739de8f4adbc7809a30b7cc6bd3f94e293f585bd1ca5a78e483e57ba32a0ed59ebf8071dabe84cca6102181e913b3dcaeb092e14b8f9ad3a6d3a9dd5919b4dc265bd911a540dd1e048806dc1532be05f137a00bbb7c499101c31f86e14526ec9744365f71214ec65e08cb8c7977658613ef8e1f67ff3d5f5f9751fa883ce307eb507127f34b52e7d1dc03db2fd553981bf35f2e226d6b7d41305c69a53ca3984d44f9a5e1a135dbb3a03ed4b49c537fcdf0f470eff048d4bdeb72ef459665e3fbf29822c84632deb3dcc997b05f2e8a663e74afc1cca2abe9bd2f0e0fe9ce57c14ee8dc96f2aee13ef2cec3b76a2c4611b63e3f4e675c746dae5da1587815522c7b13356790ea41d58ddd569ea1a38a85fb91624cbc23bc58e956087a12d7cbd3e627f04c0f84058e58802218562d65246c39bae7efc8fdf8552aaa345ba9a1f11f9e6595ec88db0043356ae8382e9fcf11368089149e7cf25de2ec92c62e896e6dc76506b4f04b64d60de874f4aa921fa9958c9e0ef1a038ce12bc120bed70f3fdc443b91abe0132f2c467155b75ebb1ed28db0b87df07912ba876ef2e45a75a3b4295f3a0f4dea77c941432bf7c5987d268fc7b6f9ab14726e06c2145df09af93ca5b0cd237134129a08a314434d310e0032d67401323b0cbb3361c24c0626ff159bbb4a53a8b56137f3bc82bde9038d3b975c0845a12361fec43e0bef82489bbd75ad0d92c0f197d9fe663a1797996e4b5c1d03a7711befcff51e67e64763a1b9193a275b87a944b623abfd5746071aa88eb78f16728406a94bfabf1b4cd969cc7c576cf4a7c025a592154c7635b49c29207dd0d3df5781e0483ec55c934929e8b957050f524e34fee05851eacc189840e63e87dd79d2b54b30f31a9376d34ad983d0587a60493f7bf9bda12d5f3b24d1874bd13eeaaca888575259d8054ecaabe0b7421f8a3027e6baa93828192794f2364480396939c55ca033e22d4f99b6e9a1fb9b25b7fd081807dfdfbfeafe0b9ca966f3e13d622157799b1907cad4a32ca0e8c298bd9a837aa0b464878b2f12d02644f0b87adb3817625645f65da47f0e4387ecdbfd70a9444d0176ab328c78c469226d8c16236dda7b87acc39c8f9a19629d993cdcf87e1eb939a0b72b38da46ca0b053e18394b3bf162499fc6784a46603d5f68c11464dba269f4d69afab7e94bb1901342fc2ac809a3a0c8e5bb6a3c4fb7289af9321925cc3e96dc63865f64a4b658afa246f18e5585a58a68025b7043fa21d597324c9caecafa25a1d17fb7e9a4d6a09cf18f78f905d04e8898698eaf0556563d17afbd79ccc28af1b7b5d3341bd6dee6a36f3ae95520f0fccb3ba2706d47d6e9ceaf5cae28021c1b80ceb46d8fb616bf576f89b06034fc305536f6c9332c7ede20d188805fe9b7e14151ec6986f50a7962e533ce4eb977e3d3efdcb1838d05ec22109406306bf4f6ee18940f90ca8c4ed0c01de38b85660248b612a15731e1c19c33cbb75a72fddcbdf06a91c57646d825453428894dfbd5954bc252406b8dc406824f259575612c3ea167719e31962e512ae54fb950f064b270e3c69aa5d65f66817f296b04fa54cc21a6629f33af138adf69c2bae0d485aab8dbaba9a19e901a2f1c47b4a5dcbc846933402ce1054e077a242ec1579f3712ff2b2d16985a62553ffa17c11b15559055b2f4fb5f4696811979654ce81f51fa7f6151460a401b9bcd8c44c0b58718dc3f20b148b16435ee227451fdea2f61e6353b63ad8fcdc09e50bef1006037b16962ed681059d180111786bcb81a1d999ecf5ac1cf7a8133c7284a25328554186bccdeb0cfa8332776dc69e1facbe6f8c06b273e439d2e2dc2f4994dec318165939bbd10ed06942c9bbca8b525394e67f1be2dcb8b66ad7777dbfe55123091aea067062c266577d507839501aa97eb6c2ae594880182762b1de14d2832ddd5384f6f2fac08fd0c1518116da22632ee3fbf86807d352d2d859f6730d3aae43dd29982e183ff39cd9d8cf63d58f582e5383c76b95f3f7712a7853879e079a4b57b356da881c803bc243bc4d2bca983e6f3eaf7b87f3d889d724b2fd60b216e5ce28826b45022aebd548dfb66361efb2a7fc75f59665932188e489780eee3f07fea6e7090cc146c2ce877fed13cc306b24f1121e09de4451aac8a4b3d0e1cd50aec79eddabbfd53f3b29847c8663760d676a21ef5620255fb2abf0f1d9107f46a9063732aff676eed525f9ebb0c9e32766338ce8bd29583b64d4826e66dc4ad1045fa10bbb25593eaaddab1814b26053a8a3f56cbe4d50b36ade7841f04bb54ed18360ce926d29ad9fd6022f76e5e9191267db3c4a47782a3edb40a5c1ebd136928d64ee40ef2ae56f1c8293242bd27bfc4ccf9c16dc823220bd479e6eee50f69599c8e96f8365526beae8f2b25faa0126d8509dda6368c80b048ffa8614eb24d5446bef019e752d22363a042c5e9311180d8c103f4111802892f6af76317b711bee70f2823da91c504bbb54fc241a3d6039f043377b316dc08687778034c90b31ae5fa0b05e871d5a339e7385461b3b1389c495be2d071deaf2a54b7e415e260b5aee9855688e96eb3073722a8ba8ff7b96224810ec73b9f03eb7a26914d36e90403f7c6a315933a6d67c8f984050108694fedc1f5d3bff0bf7f9eebbd836ac299e2fe39864a257f615464da47287c7e130ec7aa1149a64ef7423307dea5bd7504a251df669aca4489dbd5c830297ed8def44229ff234bbd4298a618ecfb7ecd4b21a4210f09b3607dc0006f923edcbb93918017ed6f09dff7f772c45f118b646dd68f632e7572eb68670a4a47eed903ca10b70bb1bf4ff51b008d0191aa555fc0e7b3aa148a065821176302b2f12c67494f345d51fded3948612184b3c6238c6d4f219f024f7acc538b8652dfbd5df32e3cc78047c2083f4ccdffa3814f3e59d3dbb2aa0de80a729ca26cbac1dd7c4138c8c7568e12b90e4825380c328bff7528e48f7904b7e81fd6b62ca4c920bf75991a0517a3dde8e7b4b5701f147c6d47fa9fe714658e75ce3a661a18c83e8305f268608ec3a6098165709fcf4b7ccd054277d5a8c1f8422ae45853163820679b3dc691a643b23761b3e49ce95159a904b5c454fc69485568ccb38be8f4765cb2a9d04abe1559025c1cd639818e20b921cd4198712fc07a7f4bdecc91aba2fe9bb820239f8fa8b7831079898970df7cdb52828e1e97a80e0fc47b90f52815b6247352dbd56d135797ea3d3ecf9044081882b3061d14d4d5e164cd5b07c0b774b4795b8b98be7c8e7936508ea4eae3f6d4a72d1ea6914dfd5ac1fdad3920ea2e369824086e140e669a71bf6a3269caf113bb71a58deebfdfaedd899a5fcb9a637c502ba6d398904d3cbe32bd6483a8884c39969c0f8d57694c21faa22a6f66f58bbaa2010e6a8e44362589bde2b899a893fdb483d659f641b1e03d66f66024c7cb6280a43a938189fe69668195b18b432cc87a623c31512692e446f03606dfbce347987d2c4d19ac34a65719ec97a9ab1e195756a5ff046eb76104169d9a458369f20e62beea2051e21e597992c3a012c8b6c7f7eac9cce31a22a35e7486ff4ac04440d4e7df03bc8b6e2439df7ec2a4254d53dd2499e9f1b92394e51ddafd1dfdedcffa650026290721b42f8ea4bc4034d37b4de283552768511f4255794cb8bab3835425b89a2b3c23e947635101002ef36b65ea791809498e321cbbf0cd0d1f9f98e1937f758aea83ef5c7f8815b5cad0ce5bf547a83f3e649e605e2ee2317d088146ba9b7ebe78ce5f70975c51bf8b8473e092c6e79923f479449d98bb13e538e67265eb950a214c4590a7642fbee6decff43093c012279303410a2c4f8f8039df4b3682841c0d2c972e3da3f903fd4f3c55e802c93c511c745c9b604a87cfedcbf263f63de70b79b7e3fc977df5d883a518e3ca39d551b5e603dbb4251b7522dabab389d3606e78a2dd71d8bf49581f7b5cfd993e9d8021fabe361cddcb7d69c84cf1b4cd072ba29b9ad8d9080dfe5e0b9e72de32d62546ce682974ef71bff599efb4c4c12587241139fde36fb66424dd8778082cfd16fa79cd4777736bb288a940a63188b8e44dd3c0f9013e56a7fc18d1a446f7253e20b7d1bd9fa90a61461fced5587d9712e4c642ef336b3026594e5789e32f99c35cb5619aa5cac4a9b8556b9f87200ded7a25da070a6fc5325c51e03add643d7bdb6ac10155d49702e8eda0ea503f8cd556044616b22b197280d7c69efabff703f19e6e50e5345bf7815d182a8e474f425d13e511145fb509b158af156e77ad1fe8aabdd5b622015e22986903c10a0f906ed306172e7baa02fbbbaaa8c3c98e3c8a70a765cbcb24a0dfccd1f006bb8600fd7680f44c6c5216c069d7e439f88294077bd05784998b3ee3b0217025c0fba7f1b88819aebab9eff3a814cb75b9a4f7be2f29b7c6820b70c64b2ec2cfdfb04cf90f9cb00c20d5d605ded2d91b828e7e99dfc403f107fbb47b8c6e29dd9e9051bfa4ee4e4b7241649099da7d024341f4f33bc80c2988c1ac28c5d915120c326d76d3de0e7379f03e2951ae0735eceed80df44dc179940b6e1a899eb2096cad31a911307c0e44a9fac406caa30ee2bebf95f61b3ab47c72f98b80b5790471008f0aa7a529c42b7c37ef9453ea24852e8ddc194102b5dbaeadd6f3c20033a5da388b4a7dc27d9a62aeb6b91315c5eb856b9aeeb71bdf326e27230b63a7f221838cd9d8322ffa4e6eb31635a35a481775d61ccffafa66ff71b6736e5964ba867ce8386722ec3025545d66d402600dff5d69e41250118a3a9f41edf32d8b781a4c48f8e9b86a37f0e99939f29ea154d9e55bb8ccc7ffbef87fb75d58ba4e2bc34b9d3975fe62de62b11b3ec8db3b4c4429aed3947d176c7dbc872fc39ada099438a78b998f23db27fdca2cd1f236868050df8ea8aef2f47d1415320299a6946f124519a1aa1b3b979c15a259b1be72b40ed7fbd583b42c9383ae62291c203e13305f4ec8f61c1b4916c3e7d248e50feb08c41cfa21e71fb09f3a6e54bd536b43d511e1f002138ace9a8218546da45ed0091851744780a0fffc93d1ae264c5e371d0cfe0a827f27b41b9c8781f6ee1a809885f13e1d98d481ac2c9dc992a4f7730ab9b592dde1c60a4526234dc0e9467cfbe20e5fe618b0c4bbe1c66efe919d8480740c70457f3d460b7880ceeab30543e623add13b06a0488adbf3fbd4cf98e42c38263e625aa40cc8fba9cfd330d7b4f3bebe4bb84ef24c6abd03b17f4b9baa03ee239efc369dfadb02c5468d877069ae0c8f5868c294c2ec52a1667bf6df08a4be3787844ee0463282444acce70b24d0a9e9a34c992fb426b53d1e553d0698171db94cd58cbc1af5fdc120e2308329d6b294c0943150ad2ac2713048499603922dfd22091c18b285abc3949a36dc508e365a1113ba7e8a358dd97f09818483df64b8fefa574984180f8becc0aafe1652a3e190397f036333e7b6a9bd2aff302f1f32e4d4b9b5775589e258ffaf257f2a0f9f72b52290029defee4febab82136d460a689aee635331a218d2f80ff55ccd6aa55c432a451b7f247f3236a99dd574f9909e73a91e140d149b20534ecee466be2fb9c9c48b24be7fe31df51e949c6c4611c2ac0031c464fa198ad7e3a803af8749303ce6671a1d2f8a4ab1420facdb99621d2fbfd489d7bd24fdc3561173563ab439fdb8d812e7334da9e6b9cf717ba50bae503c01357feef6e3980555f2cb098509ab19d8b25ee82e26ee8d04db8eaf30dadf014d56436f135cdb5a3811600cc69826fb809a5c7b0d12a2cb9446d0fed0289b7d2c185aec267a613753c97d2024c371b72ba7601dd9197c29e9a22761f85c1056a7a0df5f2a2758d8232cfd48541ad68767a42d897dacef8ee9e07c23e60057385576de6f424a07ddefba6d6cee2a59f94e94adbea0e7fb21a925497c89581d0199ac0f5d151eca438dc0287c6df721ef3cddd1ee00d9a7ae598257a2699d2cb36151eb1593be835a660c9d1fc3b98bdd4a5be909fcf712447b07492959a2f2af66ec881ad6417786c591a1e01599d6a58190a348ad283a59e8902991d064aafdef5f2a4efe7c00c9a0a4eefb7b0930c391d862d22e3751f978ab149a4d0bb682f9a8faf8f8339d65e67fd8a841628d6654fa7d5f4d728eac527dc442fb09ebf0fbcd1c9dbcc96a380165a7a705c958c5f2dccf2161cd59a81abbd12b68b0af94bfcd879a65131e487af88fb1fdee0f5412eb75d7620cbe8237f73256a9a0dedd650610535210ae8f72dfe808fe174d479ccc266e88c709db9482242208b095ec5ca57e5cbc51ab7586d42c58deb888afb02a373efdbcc2fb635e719969cf1878f70b698884447c8ce473312a1c178091c7bbd2e4b1fa147a2dabbb94f698c8fbadeea7a90acf0266b94cfb97cfd970bf3219828b28b2caf2a37f3394c368236e24dd3215de20b6825cef025d1ad2cbc2fd91e457c96456f3ec7a56b0c6b3df54d418dc3f0a9cc8b64fc438f825e970b87e7b51346c3842c2621df64376fbb13eed3bcf7f0bf29e257dfb91cb1b8eeb906179865bf8993941eb1c23c68ace4851a1aea3e71d2915e49aba09af64f836394b2086da778f1794f676fac46d3d882c6032e819e1ad488f8b2ba5273a578fbffd399a5ab3531fae616bc1c9d13a3a06ca43aa60ed8782fa083bb435ae99115868337fcf27de685d831b6f9bb5d7a562113a71aa6aacd747b8456820dcfb3f50c8760bcbd4f47a506c829bcb7e6482889b50a5d997c37652eee40847f3615473b58ec7637a1b5c81f48881c6d82f7aea966e2e0fced8393f41abe6f4b84782c785011fec011128252fa57cabe3d4df96b6653dbb4140dddfda6711bec66e9284fe56f9183e51ae14a631d40b2784860fdf5eefefffbd3a489d05f84040ec68d39719dd5931c1bf4953c8ab87c2bb65fe16b80f963d2bada4c22e34ed556345b016605691bc668258f87ca07e9cf63da77176e6038c4f64b81cd75cce52d3160d2e06104b94749914ad7b4ad34adde752ffb870e56cd5081a03e06163c4e60597f6ce6ac290b65bea3a4e1e50cad9724b9c6a870c994f25f3a41fb4cae3dfe137ad6e8be51da69cc1c04849cea0139d50b28a3bdca27816315ca099fe4d7c6ea8496f53d4a51ee3deaeb83ffd76cfe5d3364a7d5d0915235d1bfef55e573bceba657511589d26c064beea6fa92e739f003291f2d2017e4bcde62d700ee3fa63faafe50908acb8a6b80b1aacdfdfb32e0c46a07f6950c806e4a20b8f41eaefd18b9bd03d1bb705ff44b07b100addae8c5deba7cc338645b8282ca9d09eb8f26085733a6dfa9977eb4aed0d7d56388a9e1b8306baf24c5fe93edf31fcd71bba2cc1e724427bbc75e2166b04c5b3f8a8ce84d51ba0463edf7462b1d994213207e73fe3beea2f48663f927b306662dfeb4ab80d1aac2403a4394875e4cfc5b335cd501c96ba4f54dacbf4a7a81d5d5799484d107aa828f25bc044cf2046b3608c7e9d04193e0b2b48956c4e9d4134d216203e776be6f008ecbd1c304b16063b720e79db4572784e713c9319d311595844dc200830efb913b0ed94014d6d1f5df077803655693dc0b0362a799a9b1671049c838371796bb608091fda4807d021f87478558d607a4d7f7dcd962625b5cf0bc17236767dd6be612b389a7824ca5394a92b8149b231d35ce3e61b7f112d4b151d1132baf505ae09cb4176ac586addcf425ba40fec237176f58ce1e801654025802c6876ee18e36e0b8210f454ca6db44459f9aee63062ca1568b5a61ab0aac8514b3e59a72de21c41401d894f55341b1a09d4d0fb1b3bfa8c4d401545294561567869ecbddd281f5d4a1ad337b3324b5be4cec2d56f9c40d49307c556e985fb2d9bdb06e8ed376f25ab44a7fdf38495afc799ca690eca55f4e46923a515d54b086de2a1273ec376439a58aad985d0efbe4e7c63544e450f72cc966ff4b5f74a141594a5a2896c256b13052a0c438aa3a6260c98e6a0d95826fef6e6c4747d40a0517820edf7ea73cbe897c8703b4d3a7877f8ca2f87f1dfac4219dd3d7e8a5ce253570e8782e006045a7d1ae7ac9b6935d0b557762417e59f597f344407ea1fc73fc5f783037645d47e51dc17f76ec9c80de5dc98eea5b74c5f148a90fc004e345fc42e24b2851de4e0ee6f4dee695e42e1d42734c0bca819dce5d166321f7b714b1148973a8ff51d2e535da184423ca5ba572765cb1586a03d943077d10ef5d3aacbddc6645cda2d044af84b39def4dc7d25354a81bff945e066ae66447c76e9f092c2ebe41f5b993e8a409058c303ab18362b89026d15b89dcd0429135674a2d471cce029104f8a29eaad560ff9c31fc1fa588e819850e9cfa67ece326b18cb6a19518d73e8aa3ffb84c061921e88bd2ddeb9cbce623a192b06e11868dbefb2e48abac88a01876596d896af2f74854124e753790b06374ce1826c3e9996f391c18879fafffddccc74aad720c0bc12fc5f9ccb3d0efaa6d24d6df2b3cf754adcad1b96e5d22cd2bc506468a7f5518fc5516f7a513ec1dafc0d44401024a503b48d8fc15fab5400dcc504c3a6c73bf381f4a29a27a30cf14425f736929a3ea8835d637292d70f79d81e55e3049104ff8a98974d11b65084d05a0f61133614df716e3d5740c3c14ddc8fbc6923285fed5eebaa31688747d2108eca3ca1235c8abf6bdca6ca25cd766683aaeb37b15c37031febc573a989f0e4665a24e2bf77ec696acf392e7e08a53e4a0064b74be31a99b3d87b3aa9a3074c19e835e61b0765b6b9676915264565b2d25b06df5844f467b0c7fc501f259dc3ec99fdc9989ae71cab2b06ef986f84ee17049f2b7ec6ec76678f76c95f817564aa3514d72f7bc73099751445259590a3d6c7a693db9d76a8f5e7c12b5b0d5e3e25bc1a93f023bf954616db9d6c2d3aea939c0222d0884c5b784873135d588d75bf15d22d40d1f3b7df993244caf089c546b1a6a1acc8d6276e23fc9416a4c943608235738f53f9ccdb0e1864d619ddffa6494bde1997078f88cd4a87836fd317e96490307ae2d1e6147c7ebd1862a7f18dfbd8faa6e77f9119db8d1785625dea0f1425a0875f77023d982c37f4e61ec9b250dea66f764902559fac1209d60946f4aaa5db5b39149adab1514ab9e76ae8423c43294440585b56971428985a9b81bfd96a5d9f99af722698fbf7bf1ded7ae7d49f7683e279bb831ecc263540e687418cd54624613c8d7816afa404754957f7ee0c7ce4a7d76cd21c2f9eebe97eb5487d4572b9bc4a323e3d8a9613e1e66ac3e6f6c288acafec2494b479fb3fae995cf0e5862641be3edcbc00dbbec076e0d7aabe89794c51b77487e25c3c10c445442b5c371202c0cf8c3e9f2013435a93636a56ef4c009467a0f4b1c5e0607e80ba2a93aab4abbd6516f865ca6b34b215471fa106b9b0654333accd0260adca85415d11aeaf02f40f135d8f1f8bef425c34920c1c288e950f898daadcd562f271e2f0cc92b97fc9becd7e161ea238d43a4fd30bb9f7d23dc1dae6c600cb796e12584fd5461ae17dca7efa0ce95ac0c7caac2ed4660c28afe9878ef7f1dd84d2928b5adbe5c6183c2d321d74c5b725fca14ec327eb1cf0fd30a2bccd42d656fbbef92a3f2d0559a3bb038cb66a2034186c37eaf76c0d23c7b928be469158d16a0d63e32aa30f5d2c78a93c8ff5e69c972e7cdb5ee8b62169d296e6fdab9c6caa1e53a605c00b6c30b276f0601fef320b783274201df155b3a1ea8312437f42a9f2b631c163fcdea59ea622b3c866c26257c9933b50e37d84236b3055aef896cee3fd34e783a9ba32a91f64e4544f002c48cb89d3055dccd8e3576aa36bdd79557da43f3f155142d182abc94710346cc5dcfd5d80de831380aa11613b70f27208bf096f9c7f84c2032a78bc76bf92effd47e9165826bf7d84536f1162b53a167a06501d2babb321ad5a0807533be39b3f38a226d3f6a2f83ba507bf84081cd5279cbccc2ba29560095d4c70a713d0f1a644cd8c6ec9bc835caad500725660d945452c2404ded39257c1a5ac4443fd4e5e87c0eea16bab0b03a7026d7e08408969d59655d15279a3f520a22498ada7f3c1e5a99970abaa721b497e1f861b2bf7598b0d88682d28e7caec45193cc5f1c51c632bbf113853a8a47aa36b2587dba1c9fa46654396712fb8334f0e8eb12625e4d9e1640d60ed44785c57aa14cfdf4d543efea755b528e32b7a71ad4cbec06f3f41e712b55cafacaa0f4a916cbc8776b247465cb335f2d6a2ed3179aa7a58869acb3fe65c66121aadea19769b1e73b2f94d663f4627e1b61a7a1d50c608098abcd02a84714e0d3eb46d466d77ab8e587fbb85bbf6181312e281f26fdefa1dda1223dd690defeaece97f54a5c04328ab442bda74f9f15be04d3096b56616ba65eeea49bacf426386ee6141338e29cd8dc93e0221450545bba050d6e9f7544d5b89957f6936631e591651d8043ee3925ccda89d4623a4b22bd710b7706f8777c394ba92cf45bc3896f47ce612747322dae1e8423cfbb552bbd4b4c256cffe3d11c39d035ac5f8ba6054127fdda56872db21563784f88a3f9f65d08126cfba2a7d3e47844953253522827cab3faf9878663e1a7ee06fffce65d969cac7dc425dcee35e0fbb3a407b09c86af6b83478c2c361ea639cc8a643b7edc87ce814e733fddf1b29bcbcffeb82b6e84dc5e21aaba527deefa5f29209080cbf12b5848f61d1afdc93f2862a908ffae697860d5f7158d46edea9d28fda459281c540a14106841c9f9bfc306cfe2aad97061949d5ee9c3ffea6726137cc6363f5e22bdcdb8dfb1a3d15aa3590332a5d2eeeb6bfdebfd5d60e86fd2bd3fe6d7e7ffc47919d1d1d5d42a34fef618ddb23824e76309121b799174a2ee8793e17e01611e0562ce183981f2c626b47c261d091329bea5c5b8e39ed8daf7ac0243de4c888312628400b83e3494ce8baecb0f62b64453cdffc6e44e975f22874513761ef3292cefda746b4b87e523262077a0045df20206e1200cfdff150aa3cce1bf2241684503f5623085c05c47992ad654a163bf490aa3c5d85d5aedfe81dfb3e6b7523cb0b22419135efb54c8e1e49d5e5dcecd438c007cd1a40719f151676641327238c21307ff5ac4f40cb03e67ca4333c6799af879ed76f4bd28b176ca94f0e73fbcbdfe5ba3df9d5c7f4ec8f8f6839d189fbe417ec60d69280bebd534e21e6b62dccb1ab896c82eb212470a548c917f92285cdf10c815b5b437b661bbe2bf2fffed9b8edbf6bc7002eac705526a19d00a2e0c8fa4182a163d9d53a877a0254f7ab833412557d9d3f4e28ebd171a54144209c46f34c72ec56db801330ddd359e0b2b044ee0e743ea308f284914d8f3294c53a3fd772424e63df53e9b087200aa9d16d9d1720de166be10a473a5a378b500ffb68c679fe5499c9db27bce156319f937908a022c112411268943015b70407e073929cb14f52cb6dfca89d85588abbc9f7b2f8024af08238f5cb43d5c4635a1d67f42171073aeab192e80eb0fe34e494e9a92f20e305989fc936c25e35614770219f957b8c60477731bfc3d1fe4f59bc79dd814703394aa2d157ebcd5c62057750ca65aca7698846a22ae344346a93309d688479acaf8642fe7c7a7a7bc1efd914a7d90f15dc8adf6445ed5e064fdbab174897905b3120a58661a528cb046cd509898decd21ce0924a7ee167fec8fe434d893fb202368f46621bb05197eff95a2a9d6a59a7c924dd3028a978ac6eba90607d5b652af4b0a5fa4b43ef4b35b706220d17ab533989c8716fa89d2961469b39e549a4c2b2db777528a4b660c1e1d9145e33d30078abff808feb191259d5d0f32faffcd76c44e75697ac5684aaf15687fe65412879d47397c4d32b5677993bbfd5de0b7b76a82ec24069fdb008b1b945f4f5653cdff0e098696f94d1c01a7dac6277a366184ea618b94b4e8992e0ba70818b05487c33c0c6484036e4689c8397ca589e0de71380cebf0364a500e582ee28c00cd40218249953c29dff0aed94931eb9432f7302f1ae76a0e2f3ed3905c820b835999dcf73ce41feb12701dfe985b4b92771d8c06dbc908038ba137509b65718a826cbd087a63ce8ecd2c4df9de3f62b5ebfe21d1be93831e442c20309f3632b062cc7ed8bb6a3884dca174e32e238a6244051c765ad5a606096cb253e9d958bb9edcb9ca70fb0644bd6b76bec2ee93405bf96de346d945c9cd17312659824c7f683cba1aea4f07b6b3499c8ff15051b8ccc808f2a0038caccfc5c0f4aef51b4a6a03bb43c1777e1a2967bbdad3b1c9aebcde522ec6fd3415ffc1d3f3d7b97680723eef210aed725923392961f5f95570293c30de07e561ee0c8fad1e59099f394ba25ce4d67d08d3ff4007af94853505835f3bb3378e4992b101ca512483040ac7291b77618ac55ad4db38b3c9b40f7a915da08b8e07b215fd70ecb97a44eecdafa08db874652221069b99009cce0dfb8238dad6491150031a0489688778e5895cf5ae91a6dd3f8cf88e33b272fac944b7f3ededf1b2dfd0a6e5cb07a3f2ccbba9bf67cd8c4d1e138e455444aa64f1f38c8267b4958a78e1502cecd51e4914642fa4769685ee3b1d49baeb22e782b63fa347e3f1805d60af109d239117a5382b6e952ff6058db05ea9ca9ecc747ad828bb3844645c565bb55ec1afdff9b8aacfba48bdc5a66fd846ed4be31f5dea9ea433e7bfcd8e9b42fcff263edb37ba70a6d4a686b14f96b3678accb9d451c24f9ef96ae6cdda7b3828285162abb7a5a86794172eb55ce782953aaecc0023fb98f42699839e36167544b9ad6e0098f6622843a342476f2a77097ee9e864c0cbbca566a49ac6f22d90b7df25d281caaef09bb29a18e0c276fe7f0570f1e80cd98faf8bdf3f0039304b2599be4c5677b1275fb2e86a69fb0fdd51282d48161c3a34ebb43167c4cae1b734958441a45d7c530174226df457fd0bebb45c85ef54b471afe2fa270ef45aa9263b14233bc4ade3fb85a2ab5f9754ceab7a149777005350945075e1dbdb61b796d2647e6f8698f5c93bedbe21dfae176408bd64868c47173f8e61d5e2c416e79533c4ceb5ec74761337bc5e8242822baf281067614856130ef5e7b88e15fadb8803c86043986541b3c7dbd4ec22dfa43b8bd6aca2b4d64d01bb0fe16cc276be99ac2a7a1680ec91c5877d37e600891d25b4ac746fa1296be98fb88d8c35328996b61aa6d3b02e2259a6f848114c2bdf66925b7fa82a579db752d01a00b8e76d339a4e91913477f7c7ae6483642822944bc92533fdc5664353a9d282fd03ddeef778e55ec181805c062dd372f46aa4c25678642ff6dc4c05f87455a7860f95fd0223cb7b30b3e50be38f0101d40cdd7d07537e286e24cb0870db8d6861a1f49057be2275d8fb153b7c7254b6eb8f3a930ba0633569bc474874f8210c362e7c40b3c91b30d156fcb89e1c8e510d41912cabbb71e0962019a8abee753f63aa6c2d953d2deeca6f8b659e68d1fb8f0c260d7f1d02d64908721167564e1422841ae4bea6bba3b646e4c9c4948b3cf34641cfd78c5e6d05485b47e9dd30262c4244f99ecc8bfcda9108fc302150ddf5e2979b2e9c4cce92d39ff57c2f338f494995501bea8cceac6563ce0ff05750c7e94da17e0232cbd2dbc283cf0722372b261e544542e4e0641f87ef67163c03c89dd4aa38a394f684678ee133875822e8e7254633f2334bcc396c096bc1cc12cb021491b41be8c06f0b5ba2f863b9708cd9f51b9658554d87fa406e9d80ed0865762ae2ded728a2278f9680a6e995760391f5bc83d421133de287344e19e3b64a4c54ba4b4e1fc8e1cd77b1738936245b2ed197b3756915fe492f7446cd0ddc58403a607ee1d201d7e2f7d20f7ef02fc58e42da5051b7286d4af9d20c1eff2cb7bd19431dad3cf1a19d60090c4008e6d255dd365a15eefe950d26446d9ae25b7535f70dcae85d35f8213ce39d90841890a746d51e6bbe3438af403392ef42d68fda9d07de6fad7f3fe013b9aa0a883cd3c319ea570621c6bf4873ee104c062d6d7a62b7a7d7ba5ed85376b4d8d554cb5f5decbafed2cce512c800118df850b3b7750b5a83f4a51c0155145c841ab01a02e3fdfc419e626ae9dba9a986e1bb989ae43370d6dc441ce1fc1b1f30d5236eed9c8d5b7d7dbd55066721e0bf72c516a89edb767d06e0a79ba3450860a7fcc13b8079882f13d097cc70db8dcbc65549cfb2addda47b272d309ed00f6f0859a6fde2d25eadb7ff5329320c3a24a44d9ef39853476dd5ff6165b817b1afd04201ee3b088526aac771ebd55b4536215e05507665e0aee28cf1cbfc5600090fd6b6192d923682bd339d6432e182c3e9cc4b39e3e9a88914029e493caae2b036a76d606b7d48a0ba05846042dff4b5f8516e0081268dc6c0ca9eec5de71bccff7d13573595ac326ad87ad7eeb4e2a132033bac81bfe444754417827cbacb20e4b6a201c4c9a6ca6f5cab0f818dab35f48f8c0b657b21d4608efb0d94a99d56da433b6d7b50377c6e90deacaadc8ba0ff279ba6c50417127dd6ee3dd8fd995dec1f8f94644fd799393cd77f26ef8ff9e274f663fc9f2efa52fa05c5704dfe12a6dd8f9e7355e7131bab0019946b4e4a47703079cbbca773a85db65a988eff478dcc786eefbd86633c23ba91ea203526c117a244cbdd495329c03336940801e4f430c32416e0d64d421cbb51c4742f42a0867d3c26f77ead19d71f52ce1e427121728f80eaf6c832f1d534d0212950fb014258d9375a710e6e18314f89337dbc5cb9babad172034623399108aba500154dcc706cd8298db3c7ad15db82228a01b1c87988613c32160b1ee59c004187d5983246a8d99e052866ac6ec40e7a12862d505e016291a1a887f0dd2b675a1d55c16a6c024e518ad899e58e8b81c53d5f697c519bbe482781cd3e68544cdfaf10b5478099bab25fc5fd119ddbaef3f1c6488a7fd5bd1810510e9f6b1e45428e290206ea4a29f9fd007c5c8e1ddf318bbd4db78c78482a050e2388c5b88cd3a69defd37956b6aa8d721eeb5ab5565c229f2b4c3eed2b64809ef7d33c61b81b78c4df30c12fd690e38cf4c19bd6b23577dc0069973139b71c5a85e4deada6cb08b6ed58df2d668f052ed19cf1eb708b7780afd99e9bd2699236fc0657beadba6511cdb1d42c29d3407c590ad3defe805611169db11ae0ba8d1a8ce1328bf49838609f6d68007b4bce755a6791c779eeee60a30115104b55391f6d7d05b142b75fd6c71fab7655831c279069bb30264eb6e673f9728196e78b85de941b98ffe67ab1906e7d1a22a6430dcbea9816f46625d40122c67a735e7138e948beb29f2a8b9d4858a3b282f0c548e18752ea63a2add115479599dfe90edb012d2a785deec764fb1eb3d3d59db7d3a0688d9418a6cb1bc82836018ecaf38622edec9259eb2fa19dc20add402d89f660589a19f5b8dc6058bb1a42c08659f6ffdb35e1f6c158d27d7c3cccdde53b2ff5c7eb6e47de1c1e845baee0fb9311f423bf98c926ac08696be6ea95edf9dfe8e5c3f08198a43dead890b5a585c38846a785c65c51e94660c715c205303579b19071424c5b7d7144265122c89fa86b7bf6937d496cf3dace06fb04ec31de61e4b5646a5946692fd045398af01718b171e051362c29baf1f65824ffa79fb649f3e123b682b47d7438967e991d35088115b2dc134b0d4fba00cfa19af1438559cf2d1c77a696feeae8f2d1190ed34b821130dbd62a0728490d35f9c502176cbea896c9d5f5dab2f2fedd75e1a73b0977631224ce86f9958f759b1d169f8821344090ce99b4eb28c24d8d97e796ca01570c91e6bd4291f5a8589cd7b320d0d4a95e213afb9b1b25806fce2b193261b9ea77f3681c7e7e0301a363f69b1e940ccc48c60e1d520aa7fb81fcd4ff6aefa8523b6ab3ae5ce1bb5d48180798a5a6e38bb3d9ea96136b9ab1913e1d79402a10a83094915d7919738419a41140136fdc60cd4605d32c1add34a92a95b34983defcab84ec5943fc2d726c09bacc2d8159136dfa1bb8846b7d5e5f7245bda34e20a23281cff121ae80a1470e272dc8e8aaca14cf4ae6467aad6a5ffc5744d613b3e162dfc05aeeb3926ca899d2f6af00f7998892176e97cb93bdb49409b4769d3bf1b8f7ec9c86e873eef7d78b7477ab52e994dbc284065f73e96e08c247b89783c7fd89989aa066020bc5e636448dd698e5c92ec06b8bb74dfc9f3fd005da599ccd149a72b939194ea2aaa87799d7bf0eb3e03dadb4cffaabb8d46dd499c4a36a9fdbef7ab017b850b8d7f3abded38644781414c0fa0991c918df7480cf973f4ae77daedcdbc0035339b9a936bcb0fa420de99b6e866517551229256b7501cffaf8573b640fcc1f2f718c3860e2af3f78278de742bdb9d8699ddc8497cd3edd6b465fbe15adf51eb105c6f61be7281c592c3e2b16498e0c7703df8221f6692c7223d8caf85dd1e6527539d66768d1c7fb5d15a9e2d48ac6c335231db6a9df816fa30ca50d1b87d643fa7c37ec5cc0787c4c1761560634e7610c2c1168d95efb2a7fde46373cdf7839d05142a38255bcf453f98c00872d866c41781e787568bbf3e6a7339aceeda8af7bf38469913853a299703746a1134344d0f56a58527be60bd73c6b560c8b6fdab86f15f5355152064f0c31257916f5947ef506b94b46738dface780368fe3fcc7cbdb6fbfd2d7d8cc4d23b7f7f8889c023ac9b4390e379f8f489d45d60a75a8655b708b2a733c56ab41e93b7b055e83174a475801724d02d6a28350d4e3df041d710517af68778d2c7cdc3d1ca9dec5fa1fdabd81baba7739e1cd376a23c3a85ee0a5828deb4744322eba4d081216345594d5acf9d1ccd6c755db26b0f6f0485c9eec69b070090162cf963ab07b20a80c5b4ebb4bfdf91559e88446ff502d9b47c9a1da6207cc1adf69847a22c35eb9fe071ac0d109a1bd25a0ba8a4f3dd350fd1df4d9cb6853c70104b39f416840e2f4f655d576e2c3590966376312c17dbab518d05282fba27c75a9eb4d933073e9aa54a3ff3aa23bc74584e322d6efa54f61cce8ad42b1369a29277a05c4395918f854a750a8acff2b97707c3ece343a56a77af99af90009bdecc2fbcd7806eec6d2579c39126fe9010b60b72d3089e34fe7c9a817a3d97933cc8d4fe0951f2d3ce2a32eed8f32c1d9ec57c42d2e8d7f4471c7dbf4a05fbf9044a6c7d44d74481ce4dc6f9af79714e0f50d61de2586d0fe371deae7996bf244e682b6664126c72defe08a352f49a89d2c128a633b247c4a7122f7c2ad6b8eef9d09adabca72244afe9c9ff9de51da9e7a6c904d1b1b1017b43cb7b20b7cd19198dcfd34f54ce4edf61957b12cfb0fe8f228d51f2a86923ec00160c11bbbf6ae74b562508f278b81d75c3ea240c3b1f0e23c08a48b0aff09e120868aabd3abc7443e7decd93415f488551b07cc339822209134b8b4ba4d94a453dd7c1b9f557b7ce2f06c523313c24dc0c905e5e0972aa5d8ab6b9e175e3d88613721a24b216224553e02410d18a8ad03dcfe3c0e77b84264cec533a748897df5c621fd083261c81b5abc538daca0df53f1fd0cf5b3a1a98ef3d0a9c379f48eef3a0ee0297e71190f820a49ad75da1a0889e44d329bde279c5183d52335082ff1ebb4ee74a6aec2cb44b48bffdfcbb2cf4b7be20b9b275529974ae44d2bf49fcce21779c4784373df0fa56461a17e9756bf52baa515ee023a026f927e7f0b12f2eed17d33b3426c2454e4bd128d3fab3a8e0ddc6fdd9f0ac57c7d8e2215d73eb2fde3807d18b0f771dd7d0ebcee8207c1a8317cb592d2b17c43677345c47fb59132bf99dfe4ebff703cf7996bf1bd284bb1f4c729e392c7158e4289c4297e4b7e0b44d52d54fb3fddd4b4701de4ad6516256329813cfca4c254deb918733116c6bb9211510b4e2316f888ff463762bd633e962c771a474636d1e66b033bc0c617820ea56feac148faec5ed6c878444b1b5011dc768c4ad5648f607596cec3ee8735c4fea0d782c36696fe8c0321d19ff1767858e84887b95080a58d31a081826ac8ad178321298f5bb59c00918991e143172200ec95d1722b51f47ab4576794fe67d94147fc13dbd24b3c577826e58f627092bdaa86cad6defdda0c0f577abb329c1cf7f056770bffca755b76bd3776a4a6ebe2806138fdd4583d22bf620ee70178cceef63b26c9cea7e489c57c0b11fee9c23e2df6580baff5aae49ad431494bbcdaca3b3f956dd601320abb67bd12575f0f407478aacfeab91a664882d4b4240c37dbf5ec49ed2c5dc3bc9c336a413dcd146a7802708f02a525cbf9ff3c8100b75a880987c24a3d564a22687461a8d74034e810861a61e908a24bf656ff846a185e51ea44040bcfd6c3ca24bd48a15360d7dd614b97f657d5bcbec7659352861be3d64de93885cf617f40733b61f615fa6f078a923522bdd9341523e35f61f3dc0e812d50c15e551e3cddb14bad28d1a76ce384f25e6da2b76f686fb5436e5e08fd32f020f1400d697f6a51f80d6ce1e7c08ca6cf5279e6ecb28ade1697ae768ce52e4ab3565f27b5489ef530c5965e7de761f1c50c584f72cd8ad18b8f463065a83efac69c3ad24a9425ec9b698ac77e71a39376c03794cf90e99067536abd78d4a8093792349f376b1d99607b5ffb6f6c7be6d53b4e03f938ffb54fadb62047ffb75567d0781133a39e6cb5355c1c72a8eca91e593b2c243971f04b2b5ef05d3dd1b78ed6b798a9e073d991723f7becdd62c9be2c51b2d515362b42e38b77ee0676fa2b0f8f31f56e5163dceedb2d492f4d787d2d9c0d98a0a94295597f78a75d60d54e47c17502f778084713f12dab7f42aca2536dd7dc98ff338591492c9bb5b3c7675e0e74c7cb57786fb2f5527ba8544f722ffa2e469dbaa4ba028e92718e4acc3ab4d493a1230f47801e771a8b5726edaacad3b3dde19c47374bcb0c1e80398c8fcc5fa60912854e92e17b55f3ba758f5330ddad6a6d2bbf63a8e307344aff9dd05d1757cf46949adf924468768e3fc03c4594fb5b1e4739b0d2df4681f2770a60f28795f20b429f8dc76bb79889dedddbadb80ed65900c29295ce4353d858b2966bc243cdbd1629cc7e8c611452cf8e59973da842ceff565671499ef8e305c2e1e7a22b461de192def800477051f7da530ab707ca88130642cc4b98e81d2b558563bddde5a8131acc7848b231d117932a06b054af04192addc22b2fb504ef726c2be596fd319596b477e94ff37b844eecbe44bf895d3db20399667f0a056f648c755dc42b1f6ea248323d7612291782b4e9053a7c85d7560499c6c4aac88cdd6123dfd9c0a99634f8a8e23b32fb7ebb14bda023d86111115f805e322760beeea1067dd4d1c0daec97276fb4c0419231b1c46f55d81d9c9172b12eaea346fcd3c5fe667eb85fabbc0915aebc7cd4a47292640dda39196660fbefe695eb7704c41ac4f3da3e32f56a63e99b902ff7d589e6e730f7079b26e629338576b969d41439a2073eeb9302a74f0fb15d770f7a431ac56c55a9b30ec488221e5ed2b43fb00bfd090aee48560c5196d0540a7265bb35db7e6c0fe5249acd34c64635b27270250dd9625313d15d6fd058658cd359bb07e8c7e2f850ec315a675ce0b533d9afe2214bc7f859eba19bb53af419a2515bda958bec54c7ab2fdf661bb09d3eff5e0960037cd70fa456d62476e52e0cfda02d1677496a1312963907017d487201e66ad03dbe16e10d75336cb1b21eeac9c6a204b7d961b21f7d46abeb8f07d71fdab1baf8e89651799a8c67476f49d2c3a52bf5c9e64d8c8dbc04d3904421d8a52df883a50dbc411643b94d5f74162bed9b692fdd5c7702e03301e0e487a7dead5ab8c85393c7ee09f91b02134b05779c5d4051f74e3b4af1ca97c77e1c4d3de3abd322ccda006eb236960eadf713f12a237017b0ccdf51cd7cc2fce819ff40fa8b81c8110981a50a5da464487a23b69ab95f4df6276f84c9645e34ca7cea9966ca1945665e4a536ea5389aa3a55f2534e5db226ec2f21544f8d9526161bc5925923388c7eeda95d7d23d21704822d8816ded16c5d8660092fa66751bf54310d6f5c60f565f24ce12a7efa280273075f5baa978b4464e7062ab6a754940bc684bd61eaf6fbe4333433993478887d85450d4083d6c3d11151d00be65472da31d953ea75cd75245a6f5ad8c313dfe68ec39d658af682a501450fc194670107caafe879852c6d60bfbd317304a3882a4839b025a6cb34c55175134bd2a811fe6d59bccbf2b95d8f956b7b15d29331bd8c2f3e0cdb11b6ba82f49bdc840f4a7c27a12f7f64602ae0892ada3b527bf28e283a90cc54d78d62522f2cb847d63d8c1163d67d87651fde2118fe965887d13e5b46cbb596a56819a61a419ef7cd2532088a0e4a78d33135ef884335c497054113537e6b913ecd9c1dbe29ca5638fe6ccec6980f821961710baa22a84e7015e34ebd7f2184458942a8fb253a5f0406830b2718237adcf12baddf81aff6776c7bd941a682bb2f430b83e8f3766ebba83b09794ccb9616ecd235404206e5e70df2cc201651e858de8adf6a2aefd435b19aabc7170a8fd6b35667a645d21c309bec1f48105e888142f4ad853e27e4072c89ab6dadfbaca36a31ee461619afd676fd1174d6470efdbab6ccf6cd775870cee0f0f5828ee8e5671d504652c6e5bc407455252f8d8c9ba5864349cb35bf489710ef1101b3bcdad5dd0b8ec2122496a9a693c2fc769be34057aeed7e800a9ad1affa41d618a805ff3210d552c478abae869039533979c2549502afcf8d01e76505d689e12267b3ebcdf2036bf84650e2ceb407b18117cbceea7f9c4fec7c927dc062151968bd0889eb52938f043c0314f1e6511f8d80f0e0e4cf604ee4129aaa0bf75f772963aa493573e015d20be22de6160d54ce1009d22f11a39fad26be5a773d4029a67d430814f55cf961d999dad94c95df695e258d0d5efa638f18dcf2f9e53a75a8e0a87349be6069080367f0057e607922e3404950d286be7650f3389c6238b2eb7eb3ac911d7c93aef0d7e0ddf430ebd40905ad95272c843f6c0f716d79b553775f8f8a390d7cc1aca8f776622b5b15678ac6b25bbe43c5cf722ca065a5a0fddca7f74b2878f252ffe3b0d75926cdda94a1f7bfba6247ee1bae081082ab23216767d66f4c39963b3e73d96987e3693d59dd762ce5aa3dfd83a48678c59c88c4aa0dcfb2f9d83df1ca2e18dbcbd0084235fde8deba925d0f24488198f56b1b161c070059a61d316e47442c1d40cf64473749ce1a91da98a6510b7eabde66770195b493a0fb24745d970682e6e5e7edbee9fed644a7c96ab446652f4bf3f17bcdf82b4dae80ab1e8ca8bae1c4f3955dd5c20be12242845aa6d9f9a7d252d019f2bae51cef7d0555967c5eb10ff47353ffdd5b438597c669d5a4db5c30fad7c8e8e0d68de1fd69cd24dfb4c2be7088aff04a8609df0662d287dea0ca1f0e6c0a5ad86e415aebb2af282b407d03a04e56a3b78fcd7baf96a402257c38f86bc2ab1e37b7a916d0405184ff0d70bfb4fbc59ddcde1d8b6ea393df5f0fff3496020a628abb206536e4a8b50b798e4efb6fbdfa889f149f6c743666686896301a3a01fe9d48582a2fee930970f3802f39abd5bfcab98c69bb4d8d848b059fc91b9e566baa825b68dea69787c8f3bda8eebbb28d13e3eb44ad64eae97355b92fe66cae114a945e052261786175251682a220727af4dfb3bd7bbc89a9ef5ebb96a2ed6fe854a315897f20e49cd3f7adf8516c513723239747e687c14deba35bd71a0ad8ba935af775341b883b567ecfbeea53ede5a4f93d07197228e20d002694d7a9966e52735c6022762c8806f1173905a4d84ef9a17210077b7840cc2f82f58ad443166699521c18efd1b3cf813d129e956f04ea6b1f69853838b78ef2ec5e54e8618694d15bcd8dbfe29d53bcf70aab49d79a947e5ffad2249d27693cbce811969789d7342d7eef949e1a0443f87cd4fcdafafa2a5818de2dfd39070fd9d05157c6df25657d0640510aa77d6ae2dd6f977ba3f5d9d7e8bb1062b5ba51cd94f799c40f894aea6098eda4bdd26b2f58051a4ede679f27a900a71d02fc012fb9c58c8c88c5ef81063a827c2ad703915b417dc4304d8c7929e48037a7350bddafcebc2af267792a16061f0a98fe2afe0c2c3a96376d28293933ef3708357d016f19b1bd4ea1597edb4ede2099e8d3ca2c8eac9fc45e07efaa5504c15d838e68fc5c9cf3ba8640d2b1e711485994355f4f0c4580d4da900d2254f11237647f4070c8dd6c41e51d9536736b9642daac11ab69e7479fb8601866169ae4aadb7583204397a37f835e954216579213ae8ff5b959009aced693287f9453484d86c3c0ffc74797c0368439e2b2cd02a414a827ca75d0118ca2181b6e77000d8fd766e8a52f0a2f19a683fe2d9af52c404842898273177dcf36949c0a95c53b3d529098535a751cfd15839fcd774ca6f4d1045cf6877120fb9e0a1a52885198b17014708df7348f0fd950a22be705d0901e69d81455974f38f211f1ab0e33f3592e8db6d1dad029b63334f9877b53a4db84572d481a37018a8686f36f1b27d038c90389cb3c8dcc55d09159759d169f8e63210dc0c80938136dd012cfba8ad4181f33f6ae5eb36c4cd63d231cb04ca54d9530d5fe1dfaf9ba48d3726e6b163232563cd512a2fe6796e38dda8bc1c1da0ca9e104bffe1d66552ba9655deac1d5196490409d16e510e79c5592d7291eba8914a4ebd6a6b6841410e0ce588a08ff776613d41a8c10a2cc9221d7d36a6306566375e271bf62a69ae84ce1624acc239126d9ca1d716e75eadadbb149f5d262ae435c04b50e64c5f7e2b9145741590d23d35b5181e80d91a6ef77fc45b01d3b15b696f1475d17632ae39955bfcdec1e6f97c6f32134d1be3151918389421be55b0ee677b395b0f153b24dfb85958c9667fe13a6da3d0cad63839599ec84018e29116d0c76468287e87c254ca65aac6387ccd2972ba5db8ae6ed5767d2a5c6a0040019bcc0d55e7c1c66f58f8dbfe91f3e6491524a9ab0ad3fafb275901690a9275d47203da1d9d8a62214f722dbd3873334bf4f6c1f69b9fa3f1891c5efe923c94800d44007ce6ad0fd02e932a8b728fadc017df4b031a763ef14a511bb2ed730af86c536b8fe65189fa17d61d35cf48638d4de34038575ecc64f05ac6254e45e66984cd6430032c0a88ac50f1b94cfb6dd6d3736fe7d934aeca929152efb20c85dc60efe29ff11c959c4783ad02973bcf776fb02c024d38cc127c5c8a8e5afa5b2248c33ee4150d9bc1dc110e1075ee1c5b1722fbce4d5ac56358f8045f8e02945d78ce9198277ba5a522366826d5a7169dc925d38b7534da3fafdb6f32729463d079e114332cc3a4b264121fe58c27b3f5002dd52a195913b89ca4c9bbc326267b57434a21c223c0d08806417212b1a9c344653b3209f664c27c1f73dce0fcf87ba939f72e3a0bf81839f7e4f31adcfdd5d4c3135a5c1b39cd1042419d8f4cfc60df008f8a37e1fda7d3a21f96f593c1e3d104b6d8c7a2cd29b8b0639c0d8de0c73a18f2ee3f3589300888ea373046ca50a56edec5680a449f73dd317d0204cdaec60123039bc0ccc4fd0abe2cdfebf3a4b8057e1a70c1143fd6d27b515c41c1993914a62edc0d1f537884d2e095eeac35ed0548485d7dc55d1fe0b382d070ba44f79e0fe3e849988f22e326a8252ad1da3f3a2e27697bb9d858428454c46d60768e1d9a3dd4ee73812be07b14bed1354b2ba666ab0e3bcdd31aea7e11848e965558b2df4d1bca2baaa3e29fcb83d5b1f1bd214b35014ef2074df9cef7a32d8f304984802f95c6c6d30d53cf77c907b849ebd9df2bf83c99f6dc033bc3fca19bc0964e541ed56a1eb5e048364d18959edc872784e18fddd4bf6ba478e757d375160980346e4151469358f8dbfb5842bcbe47653cc4fe0435bf46250161368420a40359ea4d02f39e91ccac775bb2054162110385674e539680501e626b37af50c6f16c489e944b106507bb485610f220ec005d4a77538ea72f1e0e557b96b751c7e07b75cef4e9ff9db3746f36321dc600d4d2d4072f8b95b59edd394ca6238abe47a624597feeb4affa9bac99a652e39ecd41602e8f791938c7529f70984f68f9e71d701ce8afc50231181c37e531203a42b93f2247d85c091d4c2697e86aadd1cf4de6f7202cce75b7587b7f5a43f15eedc9777738710719dd189c641dfd99c8c6099c18397a1f14c0b2405c42166c3d6ae68d1c6eaf837689f81bc434ec91c76a6a8b7ccfc6f1fe3c13d63b180a22c4fcde9fc9ed07bc4d0ff5fef1d011fc611adefa8699aec88b9b9f348fbd8329603c9780a0666513215b1e59cdc96ec765a73cef5a55fb29d6ebdf71da3abc61b61cdaff3a1873d8596084a14f810677fdfdb0295aad608391c1a3cf70530bff9b16d8b7fba3abcfa9abc32364727ae80bdae9ec1b869d7d225c83c539a1947539c7fe33ff56eb26beadba05b967f50c514142a22d7979a63f596b6bc9e2846624e451726f04339148c9d1783a411f920b6bed1fbd2fd6bfbf2add5a1cea90be58c72fcfb9c56ffef74209085013968ab9facb23faeddb56a9814badb37f25c908ddb77deaacd191a0c7c3480cc6b94c35e223cc409c0529bb885a81e4700140d3b08b60d6b4946aa35e6876867450f673597637640b9896838adbadd3b018a04c516062d368d41644a003cf93bbc100dfa53ccd06c245c092d25e57d30c58a6e30db2df4b2f452f2b2bea6d36b476bf39e491d3b21156136061ec9713edce65386ea1cb5e755ad4d22587ff0b4b75594f5e493a1a9036ee53dd081d73eca3073e2c74308588ed28a305c3870fe76ded8a3dc21d7b791de76f47ceb81b2a537cd2bfd195eb717ef0d963c19a0ce45199d54a70fbb40190b595e17de2f380e831dcc16591c0b53f9a69573817bcd2e581eeddadd69fad750b12c1f3440ea9a023c80163d518bd71b35ee185962f07fdd9be4580277c0eda63ba017db2e40422931dff9077ae0866e915c1406ba23d408753d5b44d9615e1e49efc8fec891da2407e0ba61e85c27e33460dd4e2f692c84d3aeabe75b8b1d99fbda176f81643a4b615a0929356029ee4b9e1cf38bbe8f1f10f90e904c3734d94ef10323f4bb96717aed565c190ae0f57d8a29a1daaf9dd86b77ac626bb07e71bf43c1e665ba9121d154f503c3b167b164b72b44f05441837072416940a127f615d267f5c8b3e3b0849e61a543bafd908cba6736bf716f873240347b065959ea1156f3df1fd86f5de90aab9df581c699ffc93ce40677db8b5c9e3bfa4fbef90ba5e48364d3c9b51ba044908872baa6b75be7a169b8b863ab8878680de683c4bbb75783fe83f48134c435c16722c42a752b7665a0a9569a7d386d4ae2a8c27a3ae123fdd3ba7055fc952f99c707ec4ef12a7e0a229bdb4792242eb3f8e8da36523e12bd4057c8835bb9a76fb500c6a065342140ae1a33720a20846a7358dab2eca5ccac9f716d5a9c2a63bc182a06c7b5531a1788fdd8f4a3ce5f0263b09c67840a8ba8b58c02af9146e9c6640136001fc0af150b6794b86a9522a871097799b9916c8f3b58922539149158c2909faf018a020aa0983f88bf8a54a405402005bc55431b8b21423d9b938a87e4c1f6a5cfb7b03d3706e7587131797c01da0e73b4cd792fd2d29e31316f9594a677c0bbfd0871be441f4f05088b80a5488df4bdeb00929e1c9f55c9a4d184c397867d0ee6938f7dca1aaa1321d989aadae5fd5fa57edbf97f77f4c49feea263214b2b24feb5b144b175c42d758f5d4595d55f341acd199e9a27ea3def6103572c060a8e86b284a873d89e4c13b391bea0a2794ec8537991d4b47a82f86783b42340f06c714ca7be78513089ca29f3c4d31705fa049780ccf091e858d994f2317032ac95cccde0fc7d4e405723cbcc1124ed31da7392aa9f79a953b6866f90695ff2f88ab57c338ea21b0ba21ce62fe042b9ac737d7b1351ed6d268d2509e7e488d8a063f17dae3e23e7c5bbe32f29275dbf80f391a44220e859dac37e04914279ac955e108996848c12f2b1ee65697d02a4b2b830c6492b56a8e51311e1b5db3545e280e337854222c80cf8e0dd6b7fe1d7c3a23f9172e7a81ab00f6be808a0c735e4faabcfdad1f41b2b3206278f2e6eb5daa3c719a95dd95f84a23cc309c79fde259ccf446f9d0b0c2c48d4c407a61197bff467243aaabbe797e68d2f951ca8508c9178d7b67caa8b2804d1fdce643611f5c0c3d4471083a84d666fd6f6e74b67957cecae682092cc3d6fcbc0989915fd06e0a3b194a4985fd5d72baa9d6ed6fec05c5646f4b9b767b3cdc6ce6bea97bd62a114b82d0cde9175c1c8580b5c4dd914d2ef21ca0283002215210113554ae4cbf4473d66cd313623bc7040caf4db9cca69bdf9aa0bd8fe070a7d80dcbaa9726761d3f217d7e6fab67cbb28fe34d593ff6cb461c2b4d8a10f13fb27691ba87b3784c45cef50b41d353ab6b2666182d0e086cad35116d9601aa38346b02fbd9c74ede5c649fb01b12bb240c239d0b22a7604bd7a5802ee1ec3082166a57ea90e95d72d2b11c55fbe010b842cfe060778655d6f81d1dc2647efb03dec28250a5b8a4589ae9ef4ea785433ad6a812d9f90de50c00cd64a69a5d824d1c8a60233ed5e93e18a3f92465a4849520ba21e2a94a57b1e31103df57279811322f04b0340e061cbd184c85c69adc700b414d8d835cb3483700f69c61d7b43d21af26cd9752d25958063c9ffec1c5d4f1efe04a3f7becafadbca34bef022db8178157fb0c010ef8a99ca7eb713b54480765d9998d5adf1c0d98c2cf98b6666a37ee22f596cbed87ba6ff6c06b576a4db5d3a28e4c3a8975e330fc33a706245a11d7b58473e248156319704cad9a12718ff1c2fe72304762cc3c2e711cd31d4c84d05f28aa3a761e99d0a50102d26842ca0c48dda27929f79cd52033dff3b6d98f216116acad51b7cd2d3b71f705d4692762cf0ad03a99b1e1a9cdbf27b3ded1902c860ed7d5508829fc9659e4d71e01bef1351a401882e6589b442fad6f06c817b2b2abbe02331063405d22662a078c3d944db743035919a9fd0555592ebf1c004df774c27f010a1c87a92b2744c0f2b7c59b29f18f91975720f97d6b86098bbccf2c53c0870c3016a2b7273ebbd6d6ec4ad1e4384075e99ea9b1927b834557519872f9b0bcec509710563fb8d4dab1da5e224de6cb1e6b0d6abb6f9cb175c5ee52cc3a67fda190a457eb7c87d3541d01ff0bc7ae253fbc077650a0cfde06b7d3618060b0fb9a1d7f738e9124fa84b9ce4595be15d4d17a03584e45273096f9069265d4b915b7a2a8306f8218117435cd0e98b088873af79e817f424e2cf6c779068e13e168c60b98b49c6b819f8081679c14d6c8eb95f2d767c9711a8069695ec2302a845c74d8552216eafc47621e0c02f48a517cfb04094c212e95d8e2e8a912394c5dac60a8279ed2ed71f5b1c7e91b433aab65b7cf543284f59dcc82430c811cab0dced20a8d8504368c2fdb130018170103f91adcea30e683aebb23b8e5970d01b5c2c42440c99ee7bb6609b2031b943a3a873b5683bff8528f0d4cc26c83c340ca1cb1b70c37e335a4032e21d7605b7b7d15e8fdadcaea5c16ac1ba6f974b95aa7055c908a639b16748ef1256c5e715af2f684d3159dc1a79f87027ad316d22774bf37a22371b5024953d6718985a13b4267da1290276dde13b831e3b514a093b40288d90eba6f707ce09ea015600ab1f4581fe90d4904af8c8b92c119e3d422beddf4968fbbfc84a25b9ef295188a015e4e44d63c6ecbf29d9b69b03a923791d7cfcdf1d0563ed56f1c9567d4276e29fd73fbe73bceb9605a1c0d0b930df8ab35ef04dfebd0d6483fd2ff0f5a3e5c57c25e8dc8ce6a690846d926f12307da3d1fa36931144858554b6ec1d51b96ff52602eee5d337406e506f1978801fc9109dc1f413e1dd1ea5287a6c51ea0ce44c724f8121b812318193334490b14362cde34acbfc54c0361307bafaed3cc55abc0b272f31a6d2e2ed6a99a1069451ade0f52e44e42f363c8942f9a172176c387d4a54d233d16a4b6d48fa18729903d709c310206d3a508ef65f877fbc75116c8aa05cc8c132bd28bf639538e8759a9cda830c4de470b8ddf30f04c770caad01bb9a37ba0982b8ec924d68b10748872b7e42614a9b56cfa72419862a812b138825face6e83b3736e1ee79ec84b4d9aedc9e3493f86aa226001faf5f0525bf1f677867f0cbb87298a1a5495eca6a38381b84189b211c3061c75c041b6ac8c12e4e32ea3b4a24a93faa14f2489684dab5fa5c816bba61d953446f062fcd633b955a9181a31ad340d61536abfe7ac2aeb2be8270fba73b03073f43159959478d29c22fee0223e918d31e4e47219461bda4a76235e8b956bbb4fe4fd254888f7bc9e09dfe39cd938b031afd25f7741526e36fca62193b2dbc824ed77b8c154c655a5e111da6b49a474b8456cfb9f9d638a6fe2986cea8e6300da73419aabda9419ff0b218484c0b1890e80a4c87d2d545781d4a82ed380b71a95dad516bf7999c43e28581e974b971f1a6fdf9ccff63356812b3496bbedd7983bf60e61e7e012086731cc2a92ca288a4adb7033d915faea531b82fa99c6679ff599cd34807508cfed9cf37053b1674b0440fd2b95bb5ff43cd0a8766d5c8bf05173332008a0e8ed5e7333b4ff132aaecc7756a8db962fd360bf4eb104a01d833ca75ac732791d6d87298a1dbf4cc92a1c7792df2160ceb1815b1bf901c592ba0068a6b5d38d453e062ebd6f9077c9945cbc4cedb8a96d48413b75f6ba3551b669a4b9ae0deca16e9a35f8d9cd2320b77d8a38fafc975ea61d71412af00fc713a5ed2a85d54262fe2c0a5a94be0376826242a8ba56cec6b0753d009bd50e0df813fe8b3fe1659938b399ae26e89ddc91eb5ffb7826ef6540d789e9b6894473158c4c6e46518392a2b55801487461c653986ebfac0d71749d12512c6aea180a2d4949413bdfb87f67a3438b3c827cd2d27c9f5ac91f90f633fde30b87d7aa8ba00efe336302c4811315764958bf53a4d37db7e06f7db4c6e61b1385f5c59a0d6340e8da370a770cd3634bbd84d94b191be5016e04715a9080cbb1b0f3945f09974d0803517354ab4c09420bd232bc443fef6b067b9ec0d54e11c15fdb75eba7252bf77c00db20680e14ecae7d62a5205db7e969c2bc447ad9cebcec651b9f0fea0283db579ae9f3d26b493e5688a9c682c4cff9110b3fecc9edfaba82f265c8b82d4f17f030e765b5f949c7404428a50121ddd4da3182fc376a4cecd3ec8c3f487c7d5c58c4a1045e526d6bd348f67a580e52a3afce9b3a836e3fbffe4bdc82afc32435333c7b2c59ee151bc24b04af625883fdf311f3153f6568a1dd6bab989dcdd3a02885740ead1eadf320a857fa9deebae98d0b11cbf3912c5b753a50ef5c90dc7edd76f6a91046af1433aa8fd908081775db10a918192acc3fd11bf138253c7e18ce0fecd73ddc8cb0d608d56b9bba838bae30f6b51b6d7e5a035b4908ee0ec39d6dd600133990386cedba632234d66628548516c2e8bdfb2c3e5f6c465c0e7e74636ef86164d0469acd66481c3cc5ab4dfe172b3ed3c9329b69f6d5bebd5345eddb3edf244dcc4b5e0156d4e168acc6ae876778fa8e3c8829182e24992e657d66d175c86675017dd1c7761a94f514aee9d41ae9acd3a0ab69d20b3219310b1752a3394aa67b493907c8a15947d1d4645656edc7d99d8897b3020968fc70a0f90150a51a917c33fdeb2bb4293e8accc6be8f21074fdac1d01317735699f88c50507c43645f3e6e29b66dca777ab9e5810c6b7a2959f8a8f11107bb8a03cc7e4bf76708f4c260e6b5c75fe480598d5bbcc07863f7e71cecdd62fe644cb78a5bd583fa16bb5e48fa31f51f546607e0f35081210df129d2feade739959f7399c47e9c6cfb74bd94201d4f4567fe66cb19f2f268feca999b0e68adade6fdce4caa4eeb522eba30f2e7df14de014ccde805891bf3a4291df067d2b61010dd94b4f8006c1f5ca785795e17488d83f3cf72f047a7ddec9f88c0cc74e764d98b2233baa73e6508c30ab7f563ce5d22dd51c018881497251a7b8d077ce25a84db76eda06869038c57d017acf26a253fbe7cf62cad19eaaa77e3da910c9b02f77c5e587da84332a9292d92ce8c6896d0daacab4add3513809ab82e8b5a1af608785a422eb1e0fe74c9a5c7df904957d377cfee2aab2d9b4cd0cbc2ff93851db764165130a0a972da89ea52c4350c1ff987a678aba7da5a96638f9fcb6e2df022cada088503c065aacd97c8e19c8f467a522340047fc39bf610f7a8892baa718889801fdbc45c372c9869c4f3a0871dbd1b319d3ee40da570815089004fcdbb3a8003bb3d80f60c722ebaf41ee8c2aafbcae280c9d9d91208ca8910486ae73bfb123c14d5fa90583743cc3e11ead332fd0540906db5077a0428c29802c1290d5701bd00041a018183417ad2ed87059f66bcec88f0488ca1ce910c52cdb058f540eaaf98d108acb90e46637daceda3f1bda297ca24db9309c49323cbd544b548be8716902f11fb3e265edd215d2239b317e858804210a149729fa2b5d2a2ef270988cac4256b2623abbf77a73badaff2c7b983a7d8acaad81372eb530cfb1df4c2e77fc3b264ff1f32a4edfb172824cbfbf2409fc915823a9b3e481c391a5b08df9660b56c0b1c40d18da08a055eda1c96f59dac4cb1f6a9565cf5a69d89a4964b613f92b5f93e1108cf73025f5ebf73b4bec6a2e9077a5a65aa9503f099b20b56d94ce6fb5491d9bb1c21d439f4eba79bd1b395becbd53ebf9d8e18bfe8a76fa4e0f9a4a6fc480917c7e10f520284b3b97d0cf8c3a3fd4258aafa9b46562adfaf8265671e8d42ebb6aa21affeff57290e904d2a37dacadf7c918a4d155fbdd9b953d2128ab2054df5734b3d659869ee083c71ff13e48722439b5f9537248c308513439bd38ecc85281d71de16aa0435e5eee650ce16d99c50f900aafa1169a41686cc020b270d78bc7f875d5136fd45e209a0896ffcfa77fc413ebd62ff0b17f74fc939557cffe24cbbb2848da3845db70eaf3fc28ead8d248f293f8e338cb337671bfbeb9aeb132e17801a27b0bc510de3c12629abe5d5a7d49e565d55d604dc68e0722288decaf3b8edcd881229a575267c190a4f43ea3ceb98872ad43db46483e64b9b795749e2f21c82c399da123066326477257a9f081af19fccac9bdda5ce0fc3fc06e5230cbf5bf09b75ee0bf322d2257ba5d624762faaf66e610fa11270a6726929ffda17fe786a0877fa7f1564a867d0b216f77e098f446831ed4e3932be3f5b863d7b23813582132011a8e5d20e0ff216d10832649072e54283e1a49764efe1aafeff3ca78d17c94914da04e8c02dd69947633c8a9fa3843f68c431d737f8ea498fbd32aa6f4498023b4e2741b8a40213747bdfb55cf5c9b8e88219f7cfc4b2cb6092581e0a614d86002811271cb8962aa5285debe1776f2c4f3c5d156be102f9d1f7017e72a02a8a3a291c6e9f1764175ccf5bf69a695945d38af898da152926d237d612bcaa2dee4ad52556f96b4f3e175fa7a68724ce5835acefe71eff9cee5e3b2556face72ca6312d0cddbf165c65fa9e7c384e226c7208bc92976341a6fd058dd9bbee35ec5820912d12ad1998ddf412b8ecd3f1538562f83f84a0c22f92faf7fe7578788253cf0837a4c3780dd1526b3dcd589be0cd9cc654d26d652f674f54c54f5c98a5b0b47a34f114aef74e3623aa31684b5ac166cf24b00091e107d0c5e0acffa4ea6c7b23484a29e391728d2402a75eed10cba68641397b18077af258bb5d88564ca153079c382cfdd609987d0ee9ca43ca30fe30aae0ca2dc233fc489154a87bf8ea877059b6aa7a47ca1fae9b355063dc057cbcc8c09ad6cccfe68c7425b0e12077098a4f259f48174b0453ebe8d7d6eaff2bb94bef257595abf9441c51d916db6fb8b8a6bb68be357780d176b46a9f4670147dc5361bb5896316d4396d6142bf04d211d94c686f370210fc12b3fc3a7e206ccd95d2a8a879881c3edac2e7821f24fb9636199072c87ebdd44e9c6c423583336ce873db020acffbbfcb6b1dbd0d8db430a1aa0aed9cadbfc4ce8e25bca0c621342c0367d55dda108e59767c45191ae72b0383f8760b10fc8a937d5f21230aeef2ed2b110395e893bfe51d19d3359e02e1d976e48af21e4eb0e5b8340ccf27aa1d74d3cdb35325e3c421cf8bbc6c1868c1f4119f8f84b3ad254e1373e28b2a9c9a3827460d15a228ea43f004e05967c27f5e185c0f70679390d852f5ddaec7ed38c2bb0c9013a6b6ba09ecb0b10a5e61a9202325b9a1efb30e09fd4d518e19b6502b3aae71364f4dc01716d33c1e0e3233680cfd51568c946d04c6e616000d5ceca219d69a1690d85c5ead3fb94df4f6c05d9f684a054b5ee952c82f3c5e73a8362d72a92b77ca92ad11370753add25ef5e697ccc4ca60b8c90b85dfc17e669f2cf15c84d12498da9145f9a3d611f3a1e4ab2a2472ffd1bdd7eee8835175afd6964f5e7d6f1aecc55cec4a9c4cb4a8082d50102f1d1a330e55b884a8c878c2ee932677daa72ff344a93624ea36260fd6c8a6c14ab71b779642b5c50221463308929583b9bee841dcd32bd728f9fbc6984985921b45400fb3931f4a4fc70f1fbbf53892b477943fa49e790d0e7811cb92695e809a123753b6f91ca078a612770705aee6546242912878341c646d2d0aae2101272b678341afa8d4834b65d9dcff642bda3ac0ef1369faaad9f1238268699658bb843a324da2236b77bbff4a93a4f78c6caf70eb749c66390a6503b4202b8cc455299284673cae7ca937bba945b3a0e2be3e023bbec149a8b9387a8ca056bdadcea5eb6de8055f1184d94461f601825a3252cf438ac3a9a9c91f8eee193b4ea480939d52cc3ddffd9b19116df41abb0dd18a8f994ba7a77e1ea1567b29c1278dd9d43f6af506c3d8060c578ca6134382892280bb426a9e8ecc431d5a33d60a17496bb59b867a90aad4faee4607f51f806ccc3f818b3a9c2a7ca5810589cbf3c919981ec46d4b2646a4d9f1af0d4ae30381501f551c8ef6a6366b09cb419a2d40641d57a8c360aa98230d13c0f71173abf449a7c0ca274860ef6ad99de6c7217d6d949622c4ec20200630b5ea7c1b3fe24797043b24b018c0987f0c95e80594dbacd82b34de7866c4e58add0fb4405959360dda4bf7197c0556c9a280f7485aaff6ca21903a5e9771c646503f81622df46973635134106bd786954e174738f71753ceefaa7ad53ae1a61a343d63f896fa7b8b69600cd2bd975903c4df43bd1113ad661e4eef3a2de8f9a0e01e4320c125dd9983bc71eea2477bed7762fef8a74a4c38db46b5d35d91117a6134484a6d444b1a60e7cf2b3b55af835a277c1db65dfa23abc2bd1d75a76448fce4c0519f3d73b531847aaf9d51aadceadb79dc18dbfe2d1be5af38d019ae5b6916262e27a2a4edf9edd29b96d1c2faa2b30f2100f6e122ff324f4cfec69ab323a66a13c2b34f37394c8ea67a0bb1beef3efef35ef63ce0dc1ab6056b30f0d4f0f31f83d5f7f56b19115c6986b56ac2a9925a7866b015f7f26feae2d715006b286a80978333c7091fbb801dc42f2c2083b7cc72d1254897588d96137ca30194ced30ec039f7659fc142e5847c0ef7ab877b7a0878e69ab9befa48ec75d203aff9a3e8c522ac1e1a7099eea9f4a94b000f767d6db5f4530c48994db770cdbbfb8cc479fd45571f494543ed76db1010a2b22ae12eb561bc0cf00d91b233a06db14b110a1dcdd36f4cc2fcab22d61a31e5475b59c7afd270a56cd8af8bda84c919f42c2f2d095ca8dfb508336b4c74f10e40ac79e0937427b98c09a2613221d6cd44301f51ba16c8fd64c3606a84bf8ad31fb2c8cb90812e0ce4431ce6891f75d759039db82ec32d83bdcfea65898d9359677829c70358339d8e2157e49661a65ab2ff6f8ad9d00300c7194bd98356c377676d30d55540a4f0542a188aa0ef9f1dd6aba70e3f61366a1bce6610194d63b1a90551893abc8641b9252df896e810039dba2afa134dfb78dc1abcafc3d5de121fd72232a75d7477c1b4fe2593d3588daf5eccf4051bdbd8aade2d114cd49a7940184c0ede62b517ea88f8bee169205af19942fcb27bd005311494b9f5e61890bfdb1f53b34c03940dfbf5de00a87eb33f8221e89b3e90d637c6194a6d9336cbd3c63c73beb5abc62376eb6c6331d9dc5e510e4ea3885ac47f09468351cddf5254a2d01cb49b407916f291645e9eff253c4f1cb6159bc8a14c971e6949e94cb8d250c24bd508b104dd12d1df384fee2ac540b86a6c35d76ab40e58dc255d6203fd05f47df90e195b4f266f5f27fa6f248c00ff13a5e898e0a11707aa86d7a6e340bc5e50d1190c4a43859add633756785270c2d64163178f63b07bdda1a8d13c6b0778f95ce1ce593f341651e933eedff216e4f0a14f0e8d0cfe8178760798eebb642990777c734db526c5a66a1fcdfda9428e4e6c7f1fd4615cce6c4edac49f1aba45b8960e711b10f6a7d93a5e212da8628405f610cecfbf196d78113143da02f8107800619b5a5318f6c6ed0aee9dc93599ca382b9fcc761502077800246a42d72cca2f1b5832241a3364f9b12557ae454f041b2263826442d56569a0d6e59d1fd5e3047aa23a2898d76c3e88930e0d48df35eb2e864faf19c65d5bfebfec1773a958c233ece4137ea94a22907e412e43249bde7599d8bb3cecfd290250f238c44ccd582d7fc8bc8c79d0a2eb16d21847e0a3bbf069ec6d65ea9388493f7a149451dee7e1d1ea6600f0f8bda103ab00518a1d5c7e17f6459d846f402af6023cad6954d8aa413e57f7b8c63b3e3fb9deaa4fc96dc62df5eea861520f603782684933c4e37f2d184a36046f6e4dc46129cab462d8b40dd2b6e8ea030324d0babb801cd90a2d2ac9f3119f99fbf32746e28391beec67a77824a2e4611ef7490634b70e7573c1bb2f97a82f135c51a20bedc77f65de16d934f6094795167ebe404220f35568f83ee642b71b376918429ab5d56e3695b1e86b88743c0cc0cec8bb07c2c6349fbd575e4dc1d1dbbcce5042cc75507c8deca0ec2d57e1f0187e7e4ddbc55b03c058a6c179be7ac2e1b76b7e57cc4dd64edc18cdf6255dac20e4de6d97852d780ac50c25072a24e5d65466f21ebefb21f67325e225955463e5f5ad00679c1158ab7eff391a01c26b5ea7bd5e745fefb799db217a12cd0d2dcefdaede3cf348c7abe0b3ffbf1b9714b7745ee173df0df3a5c275ee6430769451ba12e8ea6ac4081f1b1dc2df518cb4bf204471fd07836f4dbd1e557045d7447e394edf8a80326e777797bbed1cff62e962a0f51b04b08acf79a8a1295cff9b18ecc5c87677ebdcf1467d89147e9545faf7de45227d12d04c8249a97640fc9fdc9293f75076ec141c5ebb417313b1746facbca104f0fe4d3644db66d41f9592979e40021d66eeedb6556ad34cf388397443c6458b53832eaaa1feec63644ee40802e1ba3d1c9de3bda0ba6763d152da65712acb3d4df5f6b437f939f6d15b72a152fc8977f15eb33ba65aa82d25dadca94b4ff69a7b94aaf770ec2c8e075b2057ef571f7904f4d78fea5296fd8aa9fc5fe10888e53d3b2d9c9895208df6547e19fe18d64e402c242a8f3ea1f4188f4d9563408da60804d2020969299bd8e32c4a6b8474007f1ca3c698062669da216bcb67819b3772981ecdc4734b626c327260d5e553db4fb1b078ecc2925d6f8b326100b1bb78b8f1f17d1dbed308fddf299e4d0a0f8a00834288372af477f809b9bf1d67b45b500fc45cdbd5fd0fd1f88440704385309b2f1d7537a44bfeb82a3805907aea62816a26f6507b049bb990ebf9319d154fddb2fcfd47ebfb9360224784417d77bb108916d15c30933700ab5046debedbf0c70dd0abd183775d67cf31f1d1f6c97bb3e090a9d7ccc16df9b7ccd0879fdd5d067e5f5a2df37f9a80dc5c934b7e306529ec00a5a0ef5c9ad010ba0dea4a6d86a1fe536aa4041fc9debf9ed9fe089cf5582504f2a6fb8644684249e76d185072a0e8f6a8a998979427b0f6f98d4910241ff3dc7143dd8d2806aa8c782efa9712e12e095942309bf7d16cbe4004ae02e5ae3e33dc402d1fa8c9f666f0b53a0a2816b5ab17465e2069686d1710b506cd5401fc0dd962063c380c48eb0767a640f924de4059057f8cebf0fe8151220812f399bd44e0641249f441ba802cdb002a735ce89df276c394351104fcdad9dd033dc775bd1ad960afb68bb074a2763af7622b7fa4b5f4384dedf3a05b30fe688bc47f456b37a81470e5b222b87a1bee61965064330cb8671a3e6a58b3d10fd3dee76658b0f351f7d1f8f05a6c060d07801e548fb576c976cf607147a83f85b9fc5a82447e7e10af747199a0c5150f4d0390f0c7deff1b6665bf0237638312b323daa32b03fb2a8d76e5ced5553118cb8e4078c4269536590ae8f797f6462f4ade00d4e8a61e5c920c13caae801e95a1a9ee7c8ca60d40b7e0f3f6f2fe9709187f292e020b1840a51682bc21da4727e68b6e8ddd0cc87799c00111a9cbb09b5bce74dbae8dd39b6b6129cb8375cd4f1728bdeed3812359ca6f66310760373a104f6411be1a75ecf7a33890c7a7e93b2f3e00ec646f3fa87bd0c425ce88905b50c0b567f485fee41c1e6722c6e8d35d84ebd07605d3992371bf3ba50d705971a51f96471fc9d397606af286df38fde07548cf532c7ecd64bf4952fe4a7f246b8fa563076a83f8b55acf5e9cc0fb7826d83656b4f5defa068c895c2446bd7b0c914defa5d5a1e53c6d52d2ad5ab31260f649f631d791e7c378a6ac71dc04b9d41e73881f0ceb9a489aeec315bae41b37bb6699a3660a0ca4c878e7722cc443aaa19611eebd08bf3c7ad9df48615404234e876e907144f666e4f0a842e7f78b0dd7068e9863a7afb41e4d4ba8fd25b90e53ab9244c5d6f2954cc7ec4e309d3de534a2c60eec2bc1dc056917044c9a2ef13414a21a27058c7fb8ec6dd60a0e5398f3d13af7152546b3ef4ccb717efe775afd761e1dec44d5e93a425eac8c18dea24f3d043539a1b4cf38805d053aab3589464bae626269aa4e3919967c6bb0de2d0317d30643c835d58cce5c0c30152556ce562bc76d98d84942cdccd41b6ab8a307a6308949f17b049a9f92a2f90a105f2d9dfdc09a7770d8e5431a91cd262ddf8eab500b5c84575f280fb6007ad3da4c6019f359d6f0f41c51d1679687d52113dac1c18100a91457a73f6eaa8c38085bed10fe96c0404a7617b2b2809ab9705da70bfb0249679bc93a4ed185d0431423ab30a5bf7826f386981363e64ac0d3a436cb91942881e876262b5874d3c2b8e21ecd324119d7b478e734f6c6298f86daa64dd6c6b2da49e766d2408cbfde44ad3ae970a9481311da2d20fb3a212b156d3975cc9c086595e9f5584810221d5e73fa48f9c8471d7b96ff692abad2a3d2df4470ea75c27bf2dea7381e2749c1b03d6bd56604d1f1506f07b09a9a1f56ab20f2e2dc43c17df1de1456f2ac035ac2d248725112564d64290f3261f787001c07b387c03fdea6c45abe20a38e03b99de9c329a4345e5540633cfc2fd524634cef278807dc12e78c770953a101ceefe04554dc326b9ba300291f6bb5fac005b6452592da65838f0d31bafdfc0c1edd81da6e2b7cf6d03599016b0f09934da97a29f8499899b9d55ab5d77cb288706973bda625d725fc3f26fabf8c84588b74846196934fd4bf56662f78f71fe259d7d229ad7ad064bcb2eb16b2fcd1b095277a9c6db72264ea8531d768d7625a0593f9941b907782a16f7255983608f1ce1ae836b287fc65da51c5a8788f393fb81980dad09819c8ad053e29175f3abe825e7a719a86c99496a83a38a9c08076cd73946386b504bba0529fbef287c47abbccb3dd0bf4df7ec9bf57b565441d8c8b9bddf7796ee7add177ac904acf9bfe8252e9402363ae43b82771b6857a221f3fc9da4a4888f5e4c9c864d1ffdacc67a4048daa9446ca868c1d2b98f01680f10d846cbf562b0cb17993edd6cf7a20e83011b274d96ee2f1acc5ce7a7cecf079770ae5e492f0ea0bd19047c5c93696c8c14a11e9a5b9055915762dfbcab76c419dea88a25a5a54b41b1bc0099951e39a988ede16e52d970b28cee0dd2f8500b0ae5470546f580b5307b13010791208e22834cb0b322e83e9e3fde4ca3a570753e6be301893994edd557470e9e57910cd64bc285cbb9cf497c6ccd5b0f37787da3f1efdb7df4f94f28dfc8b65c4ca625f0c38edc93c998bab37232fe0ba172f06cfc4cdd64a6a47046445759812762df1329a7c67e1fc431cde332693e5f557d2fa02c174f32895b0c36a253c2cad28ffcdf694959745b45d2b2363fa7207727179c5b2ab88e5839bf2a81abc1828c1d9db5204f0b601c396810780e9f4dcf6d606ac717ec09819a28b75e3c6bcc8f421a4802b96fa1f8b57635f2b0a9a22ad74db0661b919a1c98bb80b7d9b035ad8ad361fa61b6cb9348ca4aa34e6fb07ce3c355d185639fbd0b2e34a9fa46e446e553a56cf5357694e5ee5436659e7feb3ae039204a7ea8eb9988fe2fd7b134f3d663f210f2ccb4109d2ff0bdd4309c74c957ef074ce251e740a53b022b9da6534f9851c6cbb2c05ecd76d267e50635f4a8ba8841ee7ff072a0eaf54cbf759e313efeea2adeee97103c3777cc4b9c46236d542115d70a5fcd537d0f1e3d34cf3e95f1bcff6149fd99d73265be6835115f999189f701cfb8dc0207f9631576b5d4169eaa69639ed99eb323429adb56d6bea7b732f9b87c3d50c102f8a854cffb7186d8d18731129ea138e3ae8154e720dfeaed5023dd9a8890bdd3903606c74955208cbad48940d7df01610108854d1e399c8068efbefcbc595e93c9cc99ff9edcc954be13767b8547af702ac3895c831af67bffadb198ff332b002364a73de4b6f7bc6c3b6655f4c7750642d5a8da2527e9d8935272f88865b44065cc8f877bfea1cbe8cecbfd404706c540de9f3a4745ac4b4945c03d0325310cd3cd15e0bded1d231a4163f7122635b1234af8b4b190f5df0354fdc9fc592826061d07b31affbe2ffb75a27104de0e8d69f1268a910eecfdc1e9a4d5871ab4b7b942da7ac9be59f2dfb575cd0984b2ecb0725adae3b01f99cbda0f1255b76a153e2164eb6de6647e7710cdd61e393ab578bca3eef2852479dc538d318d36211dd0a1e72d7c3cd69197fc5e81edcb912294fe082da2e818aab71da0cfb56479d7381702f48bbd1f3d2693cf18bf718e0283d7b643621df713cbbdcdf39739ee09368ac5297ab7c3e6f344f4e0da709debd1cdb0bd1f5dbb9772fb5f8c9ebf91a35d3a21ed6f4824277c8504e3b5de907f552c2374b652821850b4190c8058bef1f5b8ba37085eb5e84ec63be5715623efca922ce59e4161ce1658ef58256676ee0dc986f96e3b60c59c6f302d42c647a7f5288cd0b1d5d90c9d9f46d7834c16fc5854ec4440e21f7939c07fd0e15616be0a356e91f85a0e01a39594767190498a816f2517e4bc8123962fb9ca26377003cf17541f4b180fd6462180bb49ad63f9aaafca92b6496d0cf3c4e832a716f5e919f8a4deac4a5904642f87de4cca3711720a44e304e6ee7f8da3a6a46a0bc411470adef5f79b33dc75ba29c0e14d5bda3f9d334390a3772d05ad7eae6b7cb8198a14b09ccf6437839534724c9369dbf958adce8a3e07d86a704fa5a953d61b0f74cebf28797d37496a8d3858f8f261bb20b795bb9a964eeb5a1be484d4db41d771434169aba377a7fdd9c0cb4fbccf84a4168c9459b19b95cd3c2d1f8ad0eb99b0934228671a4655ef758093073b8741fd7280a6b638135220e6890756cdeb024175881e57fa5fd9fe6000bf282c06fa1ffcc064ab2e2b9ebe78398a41177bc1d274f402d02962bd8743bc76858979e9ece4b4ee3da25c41633aa9f35a1ce372cd5597b05425a575413f616e4aed05e790a639e29ea6aab506abbaa5ce4189aee860a9979200c33d1229f22a207218b256ce5f0518ac907fbcf831b07173786756858eac670b7aa6948e14119f5f605d3a8db982c2ac774ed2a3a191dfeec93614cdc3f16fd0694a5a69388b7238b0efd104ea8597b1baca842f2156853ce325b44e80d7fdd40ce0433836e40a7edfa1b27dda4241bbfb80c19ac342cc3d9677f803a54458dfca88ef01638da4d2242698867df553173a1e28f55db7a32d5d8025eae9bc5a22d17383de0d9a8b1f4bbb823e6554ade0bfa7a66b50bf2617d0ce5f7991bc97c49df35fcc2d24707b461818caf1b6e727231b4aeb41b7d0d729f38b97c036d933b119214bcd2af57691dacfcee3a9f3c3ef620aee2d6abb03917121d36bb79b5bf4e4aef0e436f98549168bf93492915604610b1c139499468ee85aa1a34e888478afeed4fb74c999dcf62eaac29db1a098709f9f116fa752203974b6c6c4f58a25e511230a6478153547a558e8d805634a398db2d94807ed8db0c9650bb5926a8e1efd0ea3a696737f8ee2849078685b7d089ba0748d74cba5eb58c6c7ffd40ccf92ed62b225184577374e2e6441ccf1134e29888e03842c8f5ff1b4daceefc92f91c89ec76df61694c2766a0e86d947e7762bf31c1cd140691f8a78c45158116500611363adabc48da59c97975ef6289452a6cf49b2f14a1aae11d36174d4dc1d4c7f3daed0f1a3c06946143e9b1235c8aff71977e52a6595c3afa591996c2ff93c4c9d3c5b0803984f47287a293e5291d7b404186af757638defa0b492786f329b9a62b26b7de1f5a81981bec6f06a9d98b9c4e06e2a7cff28b178fd4efc357c8efea4a6b8d78130f0508d7922db638e6429195962d7570a153078b5593340387aab05c86942242c5e18c7faea1ce214161f832e22b8de01ee868d7acd6b1b949fce226643ba8a3231da5d4d86cc317869ddb2f75a986328d6af1fb6e3357c6da5f42cdf43cd3f9cd3967c2ca33c3b3bea82a9611de25ad9bc9d3be828dc08dfc6c8bb39e3f8763d08a5d824cd67b8f48f377a7454124f5ab37e656f7b646cbb30c0e3dd7927bd48726156f737eeaa3c277d3baafba66d2b6c2bc1943146c116d20e6b307cd35fb40af559b6488826c9e0f4885228a49e249f2f98e68e6f0d12bd27db5ee13602085dbaac98aeed0cad3ce9b5978626e50ab7cee1f4b6a7bf805db92bf3e6ff775e7c29ddf2bfd9ed38ae036f13d7918fb573786478b3623cd727740d987e1e105b092597ab11a977d646d16ffd25124162d18805403ff261f2bd885e2d7190543a0a097c5a43a1bd33a410808433354d12c0a926871dff6a3f6292cc50667e68120f0a491813b821f9b95ecf1ebe5c3d152fdd3e60b0c66c60a3d95d8cafbdb2a0c87f08afdcf58ecbd7ef73a7d57acdfb3396b0dcc325e323250eb487a035e0218531885bc65017215dd5d1e660595d827a7e5bd5b59145335d2486a486d27b5d929b8ce1f327e6b6502ea272bcc4fc1d2843534e6b092335599d9de489ea91415ef16893c36bafbc60139f90a9ff1d5ba0befbf4a5d87edd6766108737efadecde2b64658523d00b42cd71a9cf6373cc092aea220363c8d79a3ddb3365d6309a9d1ac34f4fbe304ddaa2e719c55a63b94813b10b482a155f42f4397a91a170667916c8585a5351d81dfe45dd1f3338f03fe2d4ce86086c5298e33a255f3b89f999ce17035766d5a92b7b4ed3eb27dc924e740a7127e3a49c2ac7cd362f90415c28f547652f216db15928a05a27c14f6272f74532efa8dbf4e8c1545e649a7256f7be14ce7d75be1257d4f90f284ec1330dee29b10c22e64f2d6b349520b73b30c5367290f29e4ac1bc68783755f406d47eafd5806fe6959a362fab28e82f674241c69d7a8a0d6292bcff3d2ea25bbbc7f82f95dacea18420b971eadf468788a4790d4f43cdc4540a5bbc837e7ecec9ab7ba6b1435a207d681e646d1ff73b064a7fe8b887c04ad08eec207bbcc3879142b0c57b591bfa71c84503b7b7d3c1187796b48dfa422c9da1e9f2cb420cb3cf6fe273c3e6df4da081568030981c8b928ad90d855f9b3ff635e485dafef78bdb4e4e94776a8d08b91c59a2e23d27e9a5fd5a2e6fda5e291157dd2317a3e44265b8d476b5a760bc37e7c936b244d457ece4fc51a3d14bcc4d549d25422f28a0e2a28748f28684502f4299d0523dfce67ca97ba6d0b30954a11f50ce5fbb78f6bd2d5fd7a663a73a5963ea94ce66dbb75f4c349fb5c958dfababf85cee3e92a4cdd2414d8608c0e96ffe862fedcb7b652f4089265b500734940ff6fcc121322409c2409ab99e405fa60f33816ef01807ab44533cc1adc614f3e7d34486c567f36d13525676b8b166ff248a095b8f40e7e1074118ef4abc22321adf20ac563738fce504d3c2c275d2eb8e7534064a5a7dfbd9580aedb4020cc3f57ee548eb0c1ec0fd78e35d523a58fb3d515f1a9792b6f70b0879b68173e9ee36ae5d14a3ef91feed9785f4c4e5dc5997f9b32f6a9a3eddcac6c21f09398ac21be181c48cc265ebb33595cbd4c1fa3bca644b104a09c477f590110545d3c06d19b8dd6171e96f13b6a7ae811a35b7ff635c5501f121b097523cbfa73b2c7805224e6796038e59a1179984ac6a5f5249610c8a43ad909459756899e6614f8846882e8237cc5b5ccd34f6ddcb5f7de89fd444f923b6df3eb86a858f2ece85371e11f95d667931e58ac6e09a70a1e4cb4d67585dd58dc3aa1cf32cb1bf78e0f2f64473ce101840daf80030b748ed744ff2ccfa9a71870f1a25bbb75bf271f95a9e7397fd87d597adf78896503c67c7e3c03e6838dd5599db4cc58fa9dbe93be4d4bfdcb7edb41db4e14e025aa7a13099cbcde52325c37763ed02a48151997950268e864cff18febebd000188cd473e42862982bf084d0bda2e51a64fdce92c789c06a909566ac7178ce049eca4f2ddf6c2674cd487995603d1a1677c909ed69f943dc194d35a1a52facbc7aab52e1227b231f0b626732a8253b36a557d6f97c7c28adcc819c3ec6cb46c688ea8641c007b96f83f8de21356c882312f94783b76fe075402c0728f8d01959256bb117b38821767c9ab36507d4275924e49e6b6a625e7173bbed3f9f2d76c0f33e8b6fa7a9afa90e26aa2581d2ed9eb94f3d24ea52aeeee058ecb84ef8ae23a3d6fc916bc4859d613d8db03104dc8244691765025447d6ad45722e64f41a834a14b9ec9519416f37ac4bc39e4152586c8fc3731bfae2570dfe92ad37089a5ec9e9dc1078bdfb1b280b25a3235b0e0a8caec70b6d34fb0b72bd89a5fd92a58b0f9214092ec9349666d89b71e12161301e191436ef490d0760859efeefca3370533065e2053ad72ff20a0be9b083b45daf71b06bfe20b51b18aff00e9142d2ac37fc7cf5a7e43fb4d687fe3cfffd6c179a1db89d850038ffe2ada6808da963c99973374728669d4b1b82d90eedb852013ff13e830815b8ac8f84fa02e89231d301d76640b4520d5865b8fedd3272367e70432d7dd79efced5d10c510246ec4b5f724da9b35ae3195870ee8d59fc6046f53b88a5e0c8e852a4b993eb251a867c4d2002137306bcc38942fb1d585010d795409577460b10daf132c93f6247f1d8185d6806b4a33e47bdeed3f5a88fb403ef37361f25c0f925ff6f972bc2293ecadb960e89b5b92dcd2298c69414393ea29a7b55c048ade0d6d238655b6499771794b44469645b228f5ec3e2a65e06e094b921e8deb2dc678ef7bb7eaf461873bdecc04d47c7df04b1e53c6ebd2c2368e07affbe4bac3066e6829d409b717cce9d87e26131253980ffefa1efccd8f0124c7acf8cee37ceab8617679963185bff6defcc917241b4c7ee9a9f510c61a1729cdd12337bef12fb9f75477df6b40ea20cc010e717f6fc8c4196619aab5c6915bcd367ab9df1466a858c91efb9debcbde535e9829a788bc3f5497aeba6c0001604eb930975ac83c8a4f2b93f7d2ecbb6aba0e9570d50ae94bfa7d7a4382fd3711de801cde051c427dbf46a4aff7b76e5fb9ef0fa11619e8e8423b60193a37840aed8cd8e9f02095cc0d9e9695364114c3e4d69b90f81c1641cbc5bc2fa85e3790f30d16a068550c65cf3f7d57c73ef8deb8346ee37947c855be81443c79318bdd63f23c84c63e93dc8d7e4cdfba9b540c4b69c21f8e6692ee72013c813fc00f9539a739e56a9b9cd57867f1fde91ecfc1f06a26cb5eae608f843f4b8c1222e5eeaa121a0be2bda6afa320a0ba68bb4f99dd5b2e9842340cfafeba761d4d461304a5b555be6b9f7a0e0bd457510287a8557301deb9cb573bc47101067cebdebea6d6e7b8661e966100ec0a3034d2836a39bc2ad004c98127394269ac5c70c2862e32b9a6afda65ef57bae0fb60ef116ea09c32a1b161f4bc2d8c0d932d0d8b08e65ba977eb0492bf715935434c6a2cc748320fd6ddad42fd9bf10a64bfc7fb8a9a3de30cda1526a33c37b98c1c68739ecfeb7e7ce6286b55bc5ab2ded5ff064fc609e4b9346629f5ebb8476fd71480b59e2c64d562c8b933aa6d2d621340a1fd44d3233d12c1ee2468202f5b0bf0ebc10eff59129b8a5619b6de383e89c2879983d992fdfc2e237e9c50042fc6ab678f2eb1d35dbcff4e6df7c6d503eba1bc1b03a933301fc291c152b294578ca3ff8d848d11c40383df3ef52068eee0dd997a2f949b09ea6591dfa6453a04474d3134e8c0ec8aae37bf9941a0109e0ca4eb637045987c971dfdd0f37e6677c7da46b092310a9dd09ebb6537135e4fbce5bc77e06822aa691649d216293b095bb50fcf5c30fa2e518d5ad415f68e1d3a2e84919ad1eafd62eb3b0fa671d150bdf8161911c65f00e227038b0378b2d7477e4f58d327c99cea035cee1e9f4650bf1f2d114ca61f9cb0c682e5f759ff74f2e73ce869e4c861f9741885b174e92036c17840bae690803580b774887df93cf0b9f6322030514974fa266e2b14305ca0eb9cb661a260ae4aea2e2579b517d2cc95dfe01b21fa3767216c0d79aa4752d0dac3940efbb1e9eb75e5507406c3b22bd2d0eb9eabe721d5be9aa83a7d6bf752fdf746935c59116f7ebb93d2d232037c7cfd795ac5fc54c73b5f7cf286db358a2aaf6c84336c08fb4f92c1c6e0cd3fda063b7e65eebd06f144cb8eb2105b84ff7b8fb658713390c9f7d48a9113a5c488367166745a15444ca043ea1a211b1a278aad83ab13eae9b180c5a8d2b0f25fd6438a76187b099f49bd91e350d1c4f8b6709558dcf1adc20150bf9a8e1564602ac5aba488df406b712e07baf5b54ed070a832d4018528b02883acb87488ddf276e964e498cae222dd46dcb7b520295e05960a9633f0a5836bc1f1191deca04b278809370be0d5d0d83f4bab2429dc0e1afd205c83810335237dbe3a3b314a7206194defb13a2dc8cd66e522d1d4c3782652d4185bf7a22b5cb1906a70709dd07ad2440cb0f1ed36991cded8c4222e3d130d7fc894053c21943398e338491582513c0db55b57ce866a04d090aee96ef2fba9e202ec0ef1a2332eb84b85d507ffa32b5a885347cc92a6d80fefe05ec86f6bc8948cf3f6e9ca0830ab0eabe6f3da16079ebcf95f47ab1b31e8f3abd76cb8a8f25f89fdf94bf427fd653a6966bbbc5799e4a4489b678048bb130edc17227ac56f742d688598135dade541912032be351c25735a34aafca97ed52cad1a72a0a86dfec60c150b3fba620da3267ac1135aaac613679e23b7d4c7cecf6051a6764627226b901fc9bcc1afc5e05eb890e4bf9aa64dfa34ed2a498d7b696e04db0f46898b08514d470d122e0ccf37355b8c41aa048d6acdb704780fa335d9a099f353f473af480da20a94b6ee1f26611f7fa118349626b61752ccd04b1872adad21681b1611c0cbea7fcf6c98e52128964b5023ec84786b55d46e26d9d9f006acac5320863902a9f3de8e53653460bf7ff3023f19025883dffe89bee73fe2711340d951616eef552094fb2a05cead2562dec63521e61736217bb079fd2a1f607a9fdb84abbca974f2c46cab4d606a0573f4724a03151d1bf402bc2c218c1f06b6995debfaec569cc8da16309fa13158ebfcbc3b73ffc10840d07f1e4913d001ebb0e4ea3f321e9590d267b20d6868957bf0f4de3db0904444bd3c8d0fdfbef3f37d35af700f2995d6a58e62150eb6c09edf5c683462e63d77817c93f507ee8413daa03874e18eaea5c3d44cb1e184fe880c4374cdffc9be4a4080fa5b7f843e4dc4d5cd813075e9a24806240a84fe47f7ad5c68eb6063fb934b7db25b64c044628c9cf3b88af4bedd3d929057827d2f179095f2b23778d1ade5a10d76701a67ab4c814b02c58a772459cc10b1218d8331d1e5c81c142a5ea54449b96583b9c84cd05e3756589c27046003279138440628dbe6c5427c80c96becd5b7989d3cd37d105ea8fc63743e18470ef57735795ba66de1835aee7f4bc7c7e7e0d5d9f134dad15b402f63a52318df8a043c52f2e4da6fffaa55d64f4fe373815a93a285e33f4752440e97862a7522ba99a1c2e5fde70252a0444970a8fd8183fef14026f5101363739d3acb3cc43dde0aaf2394ff201422c77b9c1f197617be58735064a33617e5fd54cc4232f76b6f8e68e849de12e083421de6f640dedc4ce57e0fb16d34901549f463b3b94892f621cb772d8b114181b001777ca4ca9df996a3bcafce2c41a8e0c47976e6bc1c9bffc1fad2fafbcd9e924cecb70aaee8e9cef4a787f61c29181563a9a28e28b09c49ba24c06a705aa385526e302e5f471a55b4989a4a81e55a7bee459696b0cfe1ccc09bec1b975aca2d074069c7d11f5b6ad10a0997ca73cb9f4a96892b27660bdcec82f321b6e0d4c2052a6a5352b91293f52d95236ec22943e9fcfeee4422c5fc2431b3782bd25406e4c951d856536a8992414f1907d06e3a2c92115db2d0bf3e065337afeb27c6152416c6668b2bc3759334f5f1344bcc4cf4e9507b742204ee7dc15b963091d8bf6c2eb85279dbe8abec7e5574882476341753a169fc393a01074017bd88051bab573351fe2177ea7bcf24557ba6b9a856a6e91de34a96a01436e6e1fcf2cc5cbb43bb48ed8a395ad3913f510ecaa90036322182bc9ebcbb7c157fa7c7ea826f8c375650a6dff035f644eae8548efbbacfa3410b61c8b828617ef7a1806d9a781c87d17f7fbac7f6b15bfa7bdb0bd8d9b4a0043bd9eb81231200e095ea8e240c09fa8c59b3e270733a7a7cd8de3b0ea53de48f72151cca9cd10031f957a36ca457ad47ea29ddaf33705dc2f3486f2c8d354404ee4baa3c1c63cb3e858ed1ac4ed10ed86e6548438db226459811c1482882abc1fc00390cc3038e5447c8e99d73caa140646470eea2d81a214bcc6f3f291485a04d92aa442db551089b325ada51ea55880a8ba8c92aa55587a28c92594c21f10c68e9ff46770fd31799bd0a57861d6bd8b3a3b0ad8bd87b766c687c90d526872b97e2db6734ce8f11dd0dc997a45555c41140260958c42121aa1606262fccda8fe61636f688e867d6e63e8bbc6cf2a36f2ac0d3a40d57b4dd1f9dd66b190fd5d22201f49f1e59c2b794f02c251ca4aba0ed0e8c64569c4891b6d57d3db2fa64088d1c37c852c47f2efa89cd00e4c465af3e9825a06b4412182219ae94b8dd1cc3f0595885adbe84b102cfd9f1fec9482fa50a1a781d55602749217e33d57b15cc6aa44e994872221c3d2ea5b94770c2112cd15df94358b7fbcad926bf4692fc83f46f27cab9e3cfae8ee37d76e29468dbb88a06845574f5d1c56724622928151b22ded9ebe0f1e72ab0c4482b37b7656c008d266a591becdb375e3f5608b115b4ac40e9e138afd8a5244379813347d41e583412d9a819d62bf090ec0ed87f6550b7e48fe009801ece5905fcc523db320313b1844f50223efb397338ba833e08af989e469b8850dfe61098d8b5b599df015d179aa8e704ac484a1ffc1093bf95a7e490ef24325a778d8cbd932fa4d0ad3bec317d8d83f4ebc9d2b527cbe06ea8bb8228d2a9046b9ca2278ece01dd30cb6663c9262122483dee096312f7feb7703fcea3b61eb4b4389f0c52567c93d51de9e7db2628ee46f77198c7957849dda37c0a84ee03035c2c6870262d4409e291b313d73081623ba2231e24ce414c06a2e846bc30b14c2f8c25b43c606af7d499a713b5d45b2dd5fdb7fd86bb835b8d1170e3e1a49b1587235dd8d631cedcd8082107206747c783bcadca1ab78b4f03e4a91180df5905988ce34e5822d902b94e6eafdd93723e162df6578be1f6fb7d648779791074f731a380ff754b3c215835d757eb2f7f65f4773b53c92b109d91570811fdc619a651e3df27c8b1eba24e7bce19480a4eda0df59d36d1a4f15eb89af6e9efde87e267bad10c92a357fe6a72eb681260acc61cbe4171f5334ef98e4fdb4ed76827313774ce15abde00971eaaa2b22a67775ccadda9b76403d4be7d903f8332f85f26d0ce1cc9acf5231c121cc648a434621f57634d2cd525ca13281f6ceae5e7eee94d29df4ce49749f01b5a5c15ca0f273e985a679c58af1de7ca682e3580a6ffa474161698ad38e8470785042562d0a659cf0102dc6c6a19336b955c7eb9bff1a2c2d65a54c394d4fb7b6dd225bcfcd102af772f7d5761a841c78fa035a564e40cf3c97e36bf5411874084090d09aeb588fc9d71f5491a3edc57ad3f91fb27282c3359e9465562a95cb6ae439a19c2a69a9703b5563851efd68751b71b2645b9c79ff02acee4a971cfbbc8b65bfbda3efbb0df845b0525522412d762fdba67e21f6305340a322354e1844be3621ad16cb3b6c5f4cb3d2323c1960ccd5b075b935e86c1a537484ceee0e7ef54aa8a227cd887319b21025754e08634f55492e546ba9ea8d9b0205b8a8278894f1597aa42ae63796acc3ffc962087408cda8340e8858d396de603298a935d012d7c307cfb38791b86f18a52c3cce09f14dedec0f0305d28db12ff770f36075ec32cc6ac4fc989daffa129e1c97fe7a9c70b751e1654ea1d6dde5951c88704e83444206d531c666ef65b655adfabbba34117ba3cba8bad66cd90ae2792f74a9848f0a307ae9dda0e73e36821d2b0c5402768401f7e1dce30af56a29ccec857e4df3dcc5167034ad791b26683ba9004a0670585337dce5966ffa61ec80fafa290b71f7f9d265c8753f0fa137b3cc2c96e149400adb0a0fdd8fbe2050035fc7dd24df55c9b3c84c2ff846e3504040174abe2cce5f7773ec5d2358c137beedf14583b7672d00aa1484eb28d8aec56a25bd7e7c12f3bdc6f102071448d94c18ddd66b3a7c05dff2e354cf571be8703be496b2387a847fe705e596b7a655049a06cd2a3c70a46b7f6f8e73cafd4b5c7c4b252adc888965760f46dd423b329b8bdcb43350d2f1f11fca817c455ccc5d4ac28c88f5b999cf9a9ceadc5cdcaf10a1ebcc81d77e8083f9a0dc1902965498ab447dfcaaee4542b32167f0725647b65dbe3fa99409b2e080c5858c565ae4a0591d089a08a16c2712da154896ab3d7157a7bad4ef720e222ca2447a8a3e3af94db0a4a558f138b4a8aaecde59176cd3d25524ec27fd7fd8cfe1c6d22680885ec9063a01bb0ed23c153dbaf2b72bf0fdbe541cb70a67d333627e077a672a49285b4a4a3a55d8d722af7c8f5ba7fc872aea6ac0a44bb47823e9d14e870197e672491bde2a663a04181bc76da10cecda8582cc8fea692ebae4aaabb6a3dbcca32636e5a6cd2b2b6a0ee32480789cdc2ee9b8e522b892500d6ea1bfd1f81228d732e296294642c7d0db3784dcaf998a2d0b968f2398408860e980c091c9d08f0326b4f75ac759ef89ace1a32e0a37a2c190e5ac031585459e78818665602e85e5c5f744936b3ff687c1524bb3612908ea184172b487ebf4e0967db7c7f2d6a5ed450397d28e8e4345daaf7585ce459d0baad46aa5d8a5ad833637fd599107b222ccba98093c35dd91732d62d8a568d3a14f649050d34e1dd47ba3a525f11f3db5be303a6879553b5506eb1e1e0779c0c07082b3ec18f4a7dea53e7cbd75dc284af0854e7571316bec8fc996a458ad68f51646ab4ad27ab43e830241e71069fd7a279d714afb0096c0539566d799dd6b04df844daaa5f8226e5ab7cddb95d7997f9232ac3076821231106d098b6b45d76bac2f4c0379c136d3dfa0e909afe55a6e70f2528a172437fa39b6d3b110592b5bdc9754fcdd9535d36cf0614194e2ada7878d32703aed6d1976bcac462c58dc90eb66093559ece4f73b12700172a90ea154db45d8f9ae3253ed0cbdad05c42ed2f987118dd2abb33eadcbac1c85b5ad4b671c147633fe58dd3abf1cc56ebcdd7266e378a76cef8467348756aeab6defe8b8272e6749ba2e8b3e914d0e07be035803967f14283abf38117d6aed09069dbcd24d006ecf8adeb4d3fb3f5f179f8ae0de34d5bd684dab957b67747dd0597cf8e89057234acc199de24025398aa8199bf790dfcd85680ebee35ff8fa515dbd321260127afe12e508c3e2408cdd9e6f72db0987617825c85cbac1aaa67a0e5dab17fee1c05fadd99c0d850dd53b8f3d5d8dcfaf39477ae8306f7e553901278fe8b220b2bbf9fa8edebfd1a0b67a525957bc6b130e8ef9c1345e51b9b2fdda8035d2773e0156414b1799891505ce8596579aec6c3d63e9931ec5b2357c8c4daeb811883ee6e49985aba20a595ea0a705d26444545003c62170f28cbd0d75ecb25a8285b482e4646b1b1fab843d760971779b265119dc860ba042536c713f6b24a76f58a8650c9125731d8ae258ec04d309d2736af222b522ea8c5534d6491be2454747b8f506948a1cf5b4647388a3e46c5f87efee9b79ba3e03c39fdc44174f93326e32ae00978486ce97fc7b5fb75d123c741907a52db5360240aff05f19a38436d5363de266450710bfaa2da4ffe6be81d604e2f1d4ece7c2ad03fe91eb421eb8068cfe4c935d36914f857d27268253464ed37035c5b4137acc492d9eb97f5e74405876de30b000225d0642853f08d22e7b1fa4b11588f9f8ff56cba409f0f29dde89c6084eb6b6ded76fdce94faaaba326850709ff392ac569278b5c7639e20b5628c774069467278f6eee5647e274c72fb792fe4020bb1ae5039bac7ed9bbe0a9675469d9ba1192b56572b9a622bc22a49b500b0129ef6e2287b1d31c6d06325c736e6e6982d179f045bf6666b582a671a597daa7b1222eeae125cc6012e8a4081519c59b31af03ea9ab062df5cc36345c24477a450a2b27b1a16c4a749413b2e449f449e3dbe9eead3a4daf1f43db46e0d7b30e31ad15ebe3306ea008579f73af9d70da87c73ea8bec4eaa3e2c6e3858a86f1a95caebd493c4d6b22ff81df2da1634df7cac29d8c0271b64b9d24b0aa442127eb7f9cf7fc4ec2786d3d4e39f28402fc99bbefeecd818158dec1c3910d920232d25daf08fb2b42515a559d65fd60facb684426df9f1cb101b0b0264193dd066ec7830bd518a70cc26f865bf896a4e9cb78e8ff126fa50e950ae10013bc771387dcdcda96f89d26d6e4171a1244993e0b2b1335327eb3bd9f43386666f802261d9c3f9c4daf5cf66e80224539155b0040fe58aafa13369c60db5969ec082d41bca2b8702db55682ba4b43c7b2d9eaca754a0889a46bd7eff6b69009002a7f07699b67cfa4e03a3e4c6b7bdc4e2e9bd41844560a1a795a80500af659f9af13bca0472449ab9072c939610e8b235a9fb58f3dc6afd91e0be8e4e9002059053b53281350421709bff2c85b7d654699ebb41f334a448f12d7cd4b3e5bb082f55a6e158d8b0aea10c0915af7e63d161370e3d0939722dded52c56ca1a96a439972e2558d45a6f6272116f4fd14aebcb59cb4744c26a0d544aa987f2355206af7c2e3a911316340a906447e2738538ad75b8e846b9a824546912bacf3aedb31e385fedd8f3a8e7b303371a95b2f0bee5fab505b794e761146dbee5df909c4421ae58bfa1811d3c981f9a0e4d7f4974cea95e682bcaa75309cf36c0c80a172359339b053dcdecc55090a7a4db2e9156146087f9af54a586b0364d27f04f1501ecabb6104622f6f020d5846b7e512defbeb6458e85002e9b316ebaf2e939fe124308a1919bafa3630dadc83d7fd5123bb03e4c3aa151090f41bf9e2d2efb051c02661c96bcdfc5676216665f3db9d3f72a1ecd6e52ca0ec7797115d315e832ed53df255ef7f9f0c98255e3869e0cf695dd5815f611eb01d7dc318db6afeb9c125f1968de40b2cce2157245984186fd619e7730964222cf3783a9bd5d740c10ea99fa21a44ca884f10679bd162afac261a1b66a2b82b928dd6ce8ed347e775d5cf164be4d31c3cd5c46699bd691240a644a135028fe4c06a43f4ca68041c1a35be6c67915d871b014272f3fc5a295b332e36a56e86bb35ed9078de9f38c4bacf26a75ed81200241231a6bd0ae969948aa024220216f556d6204d7f78158feb70ff439e1ed8bf154fc939e3e52d3b713b625535cb13335b5f973e41db8289ed0553377e7876169185904ff8d4174ddcfb30f1591b9fb2eaa5de73198aaee484ba7d513f8e653a323e9dee9fe400ae09e498cfa90a0a61153be1957ee2a81690ef110ed3d9f41140216999275fc3cc029fb78eddc4bbaed6fde87cc21415f4609de84f718a205a521d60fb31d4be1023ec33e0362c8a7899cc808af23967926de7ed90db9e938140456979889a46dd204dae2be57567310779e2a1cfdff39bb9241c45f5e6903224be2e017397b9797521df2bdbfbb0ee0685d8bf74cb13de0999071fe95cbd8ea0da2c9d8f224fa93c62444f60f07aabf84213144b39f8da8d36183de72cb665ebeba7c906185e35dbe11e828e8d9a39caa9785826f80f9558f1b74c7502eea51f1ded6954a99797a4de3bb0e85c94a695597c8d8152f57622089d633f16816e938a87f925459ab0d268db955a08572ab78cf0bf012ae54407bde034c50f6add66f246fd0651b88708f8a9518158e8bd5970eb89c9e306059e71b5cdd8e43b3c549bb382acc6904625905428826c74023d90eddddce8b2054cb30ecd1953f70a9f0de3aa9c3473a5f2efa0fc9e1fd49c3846f10b3d9cc48bb8b1b175ca826011e9b22606532404030af33d7724ceb0c2692ef8faeeb7a9a74e32f97f25fb0f7ba27e220aa463a62e9bc8bb1c00788f737618f1f0d8c45f369c9ddec33dbb5d2be629a49c970b28a571e1a7717f52294e6851ce302c68d44c7894803a0ab059cec30a7fdd2ebe1fa9745e121a798be4f90e7f50c41624801be56fe6b9c3e1f2b666cfd334da418a051f99ae393fefd710c3b209f9ade2199fb890835a381ba3bb64b285cf113e81710a31b8192b4b32676de2affc0ece782ea88a017e89fd288cf559a34b4b14fa8fbee5a510468f06afe22ec0014e3622c36ff2c99f5e05bec1395470ef623d502db30bb80ed76382476a8fb385789852a0423cfa947c4909b82a8a022db2fe03b3a59b990a2c4bfc5ae4ce5f7b6909850d138d5215d8cafaaa6d3e1590da30405c4ae6944df9a332a1de4602efa2c8a8a7262aa24787e6e5e02c54a4e4eed3b4144fc2567a3f6439257eaf2daf85c274398ad3ac77538c447c4d67a0249e512ddda53956f2d41514c4c6c236b1a034bda20a418160733906b33a05e714d67034c7ede48bf03eaf1c888effb29b285317871f12d5bac81f4379ab2b24ca575ca3117b719bec6cd1c9a0e7eab97ed7aaa3e205ce8cee3872a45258c2d1a4da6ad404092acd16e0f18cf1fee02d5619af3235f1844540194f6fb3868f3e7abd5ae55bb101a41cf3a4af575bf121e2f9fd9d9dd27bd6b5d7753b0832796b37d46af5721f0a215ec0ec1edb577477aa77278cf58dc92a604e97084adc603eb784c24dc9c342c42478930d7ecdee8b64ea00aa1e8ad8034b8ff7924eb368c08264ef79c4a8941457581f7f7ff522bd7861644e19d61aab2fbf41136c9e7bef5dcb5be4272b4d18cfc12cb9d2657a408da5aab9c2a12b07a6b451fb2d9ecb40ebb8b12aec862ca64282f63bec9069d00ad8a511ddf1bfc41acc134541d3091a4a6212b2126d58c5d3b525a47640c083efc4e2cf278a45f6d65ac40b586642f45a9a10bfaad0c86c770fe8f9cca45e345796a598be83ad22b3813bb69688d5a9602f66783689a2d346dc2773ecfcefb2d1909f4d3927835dfbf25891601a3743614ce93510cceeda5d46b81fabce10cca8f19204fa91eebab5e09f6f12beb7f28e77fd1258e2d205325a8fe605c7dd6f0ef34920aac4e2e717bdd578a58e2be1390ecc83e7fc769b13b2a0f500a628bf80879307ed24215112cecba44bf611e24b42bd093ee85e6488682fbdc5d6bf174f194dfa59e423891b5d8908fe2240c856740f115e71bb16695be19497efcdbe5780e7c12c5ad4ad66d1625bdd8d76ee6ccfa82970c491e6dec05dac60b6c2e6b472227ee700b84552d717f3aa8f841714c179c0c62fda7539089539e63f8f42192ccec3581d63eb992b81f452d62b76a802ac7880f6f5fce28b55d80a5ea832f6ffa6930d8779fb00ba3c6316ad387ff65e81a4d41cc4929490869d1247d177ee8eb90830062247538282ef31c332e489bfa2f1e2e7d8e2ad1f22ec6e73c546f014e3cad4a1f16bb8c3a0416a25ceacf7fde5bf48ed4ec75b80bfbb3c659d7968f836dd60ad683c4823f4ed2d4815734d0f282981884c0f6a705919db42c3419627964ced3a3490410d7fe65989ae4d85c14858023bf35b56a5af1ad409f3f038b95aef9a5f38f2affcc614195806ab6dc7bcd49f490bdd9dada07d8344e1a917b5f1361244ccc3ad32c0d2b6619c7de123f698508848e45505dda097ee02a5ed60986406800bf60960a5367054182a24381b81ad5d783065c7dcac90703cbd980d09533397c909a1782af826eb3ad2cf7fc3eb89bbc413b2ebc79dd3bab2e0e4b3f16411d0a97a8adbafaaee8b608c1969a6366422d35921060fe24cd86c9ac5343e8d0ed76c11495f2b18a2c93ea7716eaac2e98749a9de9940c8c1a6a2262b208aca34d0d149bc2a04ee24fd990858d63e8078c6b8957c94ba90b4973c5e83ba042334f65646325f412bc80bd00311b0b2ac0a6ee0ed07d84dc497a51ce90bc1d080e4de720fefc9640eb8a3f94b9fea7e0f56919b337edd6309839220f12c9fbd1a7bddf2540a52f9efe15610b12c20f8c903b652875f550d8393271ea3b41813b8a77af432eb4ae5d664469fd4bf9eb3f1a1de80829c5ac8c0013674a19669d6beabdeebc722184760684040cc2a11a7349807f8e9220a9a912ac18f83eed015ad5e653e736ddb513a4942b29bc488a5e3160a34fe98b38611844f5bce9e68ce2f8ac69fd8bbe0326ab4ba246f427964a173cae4e3e6d7b4a8ea6a797d5d80b208059f61769ba5434beba122c7883776d895bb12c70b8d7e6387e3d6b24d6f6eafac56f28cb69980bed64aa5320967a7873b0f11b6fb211db931a365e143a98e6ab74445766de607e057ffad3e47c5cd2f7e5b41ea063f7269bc89177cafcb1e19deed6b4bb684e5238257be06afe0b01da8e624d38005b103ae2f4e20151e66afd60624b505522406691f90387f1fa66110c6a5fc86abacb01acd59b9d24620a38287dc889e756f7ba2c82979c9e9656e2e8f6623e5fc6ed5e3a7f7153c7b010e499af33778adc009c60b468e9f97b15b7240d74305a8eb7bc6a40ad6e03f1b326c2f5f4f486f0524280b362ec3ab8417eb90d675ea6c171bf546f44d03952d73010b22fa444b25784ef56e615d36d046644a4b99ef5da5d25d27ef746f559ec6e46ea7c9e111ed73ac117436c37018835d5ffd832e2cdf98ca6680e45278f5a9633e0f56f928d12f383059d2ed44b84960b682ac4d76c507c87329371b94b80a370df5590db3f23380ed0deef58ff3ca67b0032b50f9b9ef2e9adf92b69072fa8c7d962d098c1ef89e3b910ad5718abaa04d986480f2ab610996947dc6f419e60268c91e29d8a94d847945d32ef29c4e2cda0fac37f2265ab9a43e3c804d564298d9ad4b53271fc89c6f901ed4175d08540341b9fd985a65919411b162cdee66791f6252420eeeeceaa398a047c55e8d137a894b2ea21173807abfa6f7c6b04fda97de6362cc7e16f32e6735e34575e3eee51f4dc76ae40d4925828759f1ab6b232aaa03df98eb9023e17d54a0bf1f32cc7b7bea18e18b0890e8884736ce7d90655e9bb767555f71fac4f9b4844e998ca880d403308e8e469b91186437c30c50efb2f1809093bf89deca01d84729ba56c2b7aba672a24a526c828a8c8697eaa8e3c440aeb0705be4d7d165ad08b75957a28129ce96d87ddf3d4f712a77e9ce61eb42cfad7cf01a1a74f687eb9b39339eaf0a5bec960dde23485df813fa0ab3c950f876e16faec8262cb1c09cf8f439faa5e64a7bab72a00e4185d2f7c266bedee56952dae9e3f8f874baa7cac7e21228cd9180a17da3c3e310baec03bcda58a3334e4e1a4af5c5bb1727d5bab2caa6489c011398c5d5a1e9edc2c09b18c4dc11286ecca067b568882c515caa3c9492bc56f54a9e3bde217a38af703382f780c28ed4d5cd926e8e3fe95938d34ee3a3c904da70ba3c6b870a3a9e9e98cad7ba62517dd3bd335f13ff9ce667552a7de20e4e45f45fa8c2f4b85333be14b95b01ee80bbca2d8cee75ec38df57491ed20c86aecfb6ccd75dea55d5eb17ea3d8b50e6dbdfffb4ce764e21152d8ba2042fb8e09c67ea9adfcdebf81b155919fe37756695897dc71d22a74d305b881f36863e6143e3d8ed228cf54fc8215c6365310eb82167d79d8baa51f52bfc48f41faaceddecfae77afb7018a885e6d6aad30b6d8d8dde78bc5260624b9bec9fd3647f6aaea1797369e39f30efcf02feb535e2c3fb0a1064cfa0929ff8579cecbe98ab72bbc24ff712b3a47fcf74495ff6ce5d9307544aa5519c1b5f94bb1cd3c623c8256fb6c87d4894bb95725ede96c45236b52a09f21815b1d7101ae982fb951658cacc82b97ab3da0a887734cc8331d14f35b51b3a8e10185f35c7b37e108128136f54eacc2c7b2c4a21d4810e56ed1ac13a5a381ae644636dbc1d538ffb87422b8678b744a9322b3073ecc7c3bbb1f7a0ae43167303436e0799185ae2e00f53855c2534806f4290ddb71c630158ceec91e54c1241d882a63adbefaa6243f483a9a4c9b7114c44aba557d6e89136dd26a9f7a2c59aa98c3de1578490e6aa81523d33dffc061f9f36a915b2c6e0d87fb07b960e35b84a00441ffb60c03292604986e2ae070c1695e9d47b4f15e87348e01ecb0edea0a8ca530df9767597a77e0e4d9b993f6596ed59cbab014848b7a0af18f77316d9136f36e4b0203a277901eafdb6f5debb21d774791da9faacdfa4303e9994271dc9b3cd4baa3715aa27f9862965c9008bdfbfdfab60e12a4bbe8d1c38f52f183cde9ea7bacaee12812d59a25f241aff39df52a5df4270e5fb83f1db3af1050c9ff02a97970ddd159343331a1d8b7ff2bf5a6173fba557ad1f8fa3a264c27a41e81897cd11b392f90112890da7990ae9366e9cd85f97d4f7274cc7d2dd6c6a3372a9bca08de3f3bcc94af7edea79190ee4e17002f51b899224110ac5a3fdb86c22365fe06180a4db002f08d63d00682cbec758c1e6d8a5da6fbde77d420fb2726975a8a08f630f20142a5627572012108fa6455541d81e4c3e67e21fe58eaa4a59c456afdd99e2e7e01d3914bbef9bdb7f5ba2569699eeb6965d66573496cb3f5ea7747dc4b10d254f10aa74f81d3f69557e0fc730699f282a9702061948c5dcf2c897891f4ea64aaa1fbfe737e1fc1e7bb2a3cdf94f33840715b71521f1a49fe7ca915d2cd4f4b3748b5153c1ad583e79e12a3fe5fdec5992b33430d45ee0f04d3eecc609c171ae1a7719c12d71256988b4291d02a9ef8d3f28f3f81811e4095acffcf1e8f478f2806b938e20b48d123ead11b353992c92e16f9d65dd25cd63539818f8ae38de0c84f6dde3db95896675d00e53ae5f53bc68bf42ac0c486d014263a01501a3b03869658d28a59d7af36892ed19f7084fcf86624965f5062202db7e0276910c7ca69852f69c77ec42ef7988d79e67993e059a9b2920f1f4f60693086b354e6aff8e2946ddfedd1770c7cb941843d29289ad3a632863ec4fa778be6b647fab4f3325fdca2f3ed8c17a5fae478385aa90cd8f06ef12a15b3dd0d36b3048c0eefaf5b6994320f79aae150934ebf919a9128864c7e1339e79e60e0993d1ce4ab4215806e102cf9ff186513a30d144ffe993fb9445da856d42bc835455947c9b80cb8942cd27dfd682715ab3890abb62fa9552071fe2e055a930d4d21100a6c59271fca911bd0958f92543e67d1ddf4c08968d35059efa34c3a38a7befa73f2d044df64630c2f45144a8c8181835d5a1c5414b2274edfaf7681f0fc734fa5a1c405cf0dcce083bfc600cb717665f3e07967a58d56060fab500c53aa5e4f032c7b915ec641d916724ac9c2a63f16bdc38fdfbf175746c5afeaf0b43a943ac0293c141b3f84886800779069e3e8d6e3a47e7ec3b2df4147576179b61efeccae2bea430c9eb534ebc5b3ae5f532d4d69bb665601f5435ab1ff47b9e753b74d5cad078f50a15ae005e2d50bf3705c2da7ea6c445f0d318be72d9bb67bb80b0cbd2d9b8012a95c6780f49e42f473968ee69dd4741c186989b52ca8b074b967c7675f17532e2aa6a44add95371b27c369ac1b50e8cdb13daf4846937c3d510be63e7e4792e16d338588b4772f77d9a3d92fcd27d391efbd578082304c532a408e09644d99a0b953ac8a69a0caca67af0fbc321c09f6b5b64c1be9a3550341e4fdbb6fe5a8ef18debf952c6966ecc87378fdd5c87037dd16bc88c0da925c084f27aea3bd6a079317348859e00ef60ac3a72e50f15dd56214f46e962af7b7395f055013b0466184e9316d7092b8bd7fed09c57960fdca248f1eccc08382bf1afa46305625604db6a9fc194e28a0b33eed22324ee7fda937cf8abc5133f14d3af7e1844b0eccf4a0dabd62409df55598be14372ed40811e25df54406e8d8152e97433f5265b04e67b31b707fe242434a86f3d0cf85c69c86584de34a2cdb979b9acc822cd046e40ac8619e40cd0b96edb56f2b566122823b96595af99018ba584b5949bca808798a0769ec8aad1e3ad4d93d220c771cb07051e1745e354dd737423004841a017df52f48b6c4d78c9145f0d82c8921d5a378bd9b393ee882671078118c57f740efd30c88ee322b15b1f70fd73f1f3f9d7b5e167586f27f5656746faa9b140208a529c53c54b66e12a6f360a1870956efae484efae25b9716b3d08e49886befc40cb9dc0e2d3fb435cb734235714719dc1b213ab9a33fb4d57719a0d7a73a2b24c1bb372181894e5b24d66b624df8c4574ffa23fbd1c94f00bbe0c36f58c7c6f21d27bb7fc2018bd66d0c79d74d263da2406837d4537cae51f09b720fa4ee96917f4dda3d2b6c37ed5138447ac391c789c5e9b841b1f4d2cb7ecc537adca9467f551166faee3b2c13813b911f5a799f235b68759e6b06387656cd7618fed6077194b307591c14fa202fc7f6cb9ab4053222441923707d11069ce4c06d35f1fcc1e972ddeae179efbbf0146548600ac311a611faf305944891bba3fa5d6b94f1fc59a32a6e8eaa7457687bd6ca7e1e4a464eb91efc77d8c43b726907fc4a4b4bd9095684a02d21460c2185c81c0230c25e77fefeaffbf7898f1887d15f0707f42d0ba6f163e1edc02ac977f1b82b0a6a8b78bdddd5218d42b348fec7c00a0028b1affb3bd9c7eb23c96bd590463f140409414545a54c00b45f14b6804fe4047b1d290a9b11fbe14a53ded07b2155e33c21d7aae4e631feb4def2ad5c58bb6a8fea06bda24fdf4db4f1d7ec2955bee5f6f9ebfc9f4868df65ac95b22428a748cd8c2946926d4fad6ce65258e9677983d3c6f9ec1bc2dfa391cdce5577c2ccafa2c03c393c1f705eacd4626ea88199021fb0c79a7e69d876e516381042322826e84ca1c5db9f5681ad0459626de4dfa3c50fc13c279d489f60218e30a191416c459f78b0d58b77240b82f72dd157791c9c6f78c50251161b25794de957c6db594cb8b5cab12957075a3cd93d4915fbd764d430a36b78373387fa627ae4de090fe6094e16954960d59cfd01fc0c65defe4afbe0a3992d166cce9642fae7c0b36a686a68154a8ef4eda231cba2c0ae8d4e000c2f9dc720f704409dd92d9e0d3fdd471c04dbf9ed766223c3dbaaa5287ba3213872e28d879c575638f567a0d407a91a80803f37d3cce5eb04068c82e804b8e9eabec1a013646cdc57757bc3e91b68502f85dd65e41838c16bb8fcaaa75a392af538fa649a29f3eab2ae71144e5c367e4d4468605e92440e7f82912274cc63b4af6746e8f92f44aefb43a7a79d43d9b4113665a9d6b5863a5b4876604481b8d66d54470f6f3d4960f7ee1789e1d4f4c58826a4f4381f3a2739dc568193a0acf58ad43601fa9221a52e8182e1636e93a19e53ce605622a1201b75423da1853b1b81a0fef36e7a94935990458cef64b81e4f7461d4650684e684e0ea6ac0cc4178fa1ac60c233d6ef17d5c0292b66ddffc9b075bbfed31c6da6868107ca45d94889c09a8b397d7cc7297e6943b479f3ceab8e4b13f157a0495dc65d948f9814862fb2f8711583838f26aa0d2f598218bfcddebf23b0c26f63ee17e7ea56c37627c5067f07674eb8694b9cc6e62e29cf182ffa37ccdc77e4fd3b7e6cdc2b00a7f22064850bfcf3a2bd84f3f92eae84109ba3b9d136563301eb02645fa3135e7754645ab71bbef31ebd2526cd05c4ba12491961f8523183695f950c56c8babf2358ef1623059ed95813f11f096c96ad837a64bedb52dbbe3b89c82cbd6a392f7642c5b3a3fa5de2c5cac95f8df124c8ea30544e3a4b95f1d83500a32a9b031696aea864a998c96e6c168b6ab8b45528a3260bf1616f156e386c76a45b756e387aaa8c9411495dc412602b70ea8b919900a7b0e8d878e29a72404bb7ccce5188b434c0531d5439f8e4abd9feacfbb94fbbb7593f2b23c7bf7dc7a47bc59a493a510545bf1b72046a67cd35b6eb6158c389762bccd7381788b75e8cded0e5f3e6952ee47e5ce9eb3bba3c0d3e01b14be04e2560df6c47d91783026c798c4a4cffa587191cd5b5b66f7529393a13fa0701f9aff54eae975f9c816d595e52375afc120e85aa9148902d1f32ce107fb333ac38a9da430c57d8fffb21659f2903619d12153651c0f160a387d2fd0ef8d07ec58884b0b64ffa58c7723cb112d194745dc46b0b8201a8b81177117c692591aa10960681818cddea8a9508f8c2682481edf1d5bc79db048dd31f4c83ece5b7e5215c9cf49fab5a3e24ef11fdb1be20059f2478b4ef170cb619c89d81cacc5d0e7c005953ef7e4d799a820ff16226a17686331acb13b5bfd1f672cc79a8a2f0a68c0d146fdf1f9f189991bad8467e3d486ae240a5574955b29256801c93a7aea3fdb978a5629a69babcd3bebda940c09431e148353e28f26e02e6b37b7654fc72593617f0602e5f28d9ef7b7ddd4f53237aac48b846aa9821b151f703cfd9339043bfc4af33be2d8200a8f2fab0ae3ce6ebeb6e622dda787fde3cc7028315424678a86424e0425fcca1a7d1ae86b026faa4d452579e6d5e6fc45231c934bba430e68b0d2211dfb666bc9fc2b538227d42a5352bdcb52bf403480194953bfeda817d1bf54ad8a63e81f3380a5ca012d845ef0403adaea79c1a73fb302569eace07db1d4ec5bd5ffa29192fa35788f5f02975bca22d9bf2def99f7725c04ea8810bcdd9ded7ed25340de2804706780753867162c99483e1b5e536cdadd25efa17c28e811903df90844c7246538415af2b37708f99fbd83fdbac97d360aa4399d3d68a1a57ea8d2c2b2220dd3111fa5499fe3e0c680556659136c55b81328fcd2971a94677167f5ea066fa380f982e1f7d33c5501ae4a63603440394cb69a675b0a14925f22c2b346f0806b576e0f4dffb24129c727a389071ad6d32f877abecbf03b751fb186905b8d9fda1ab9e6268ffe580d7c22fb7f9a355c2b12cba034ccc8a2d82d331c7e5d34a7829331d671a8ae11ab1de7752778f88d7812de3a12dfbc4009719c5d899f5090175a760655113c94a93795814465afc16192be5b29d3acd306e25a14cbfeb7fc267246e59e30703cf0baaeed33579bac042fc99626ed6abfa05409e30b7bbb9a608ac83481aa16b3dee233c3498a43cf47471851b40608129067d3137b782549377d128a55c6f43f1bc5186a2101f3670aa07ee11b99559e59e0008ca79c8bdc574c13dee105103a48ce11e080b50ea0f3b5a0cc3df6ce3726f20594477e4c0ffb04673884c8cf5c8210ae6d89aaa4a4fc5d0f11053152d4609094ef758015326a8f86edef394498af6adc4db94e71d7f68f0ec6046f059dc14f2750f18ad5224381d2b6f8d156d6d53ad1395e995501ef79aa68d6e9d4c91fbaed52b8b4389d7ea9ef312802671878f4b11a78abccd7a5fc16fee54ff0810581d23c12c50d175afaeece756edd1081d17d841172f7356ff765affabd3559074b0d8cf108c6c0d96593d734f600bc05f42a2d49aa6827c990554b2d75c138d69316ecc4e66b59db505d14996feb6e02219bfee687157b6fb9c43a049595486f5a5e977c2c2ae6db48fcbdde60e94de4c9ee42ed13bfb0263309bb47e9f5292f3f7825064d5479812214d709d779ef17db83ed76c65f4e10d63978274262bc4b7151880aab9e78f8b8a8ece3745b534fd9462903f6c59eedd63f1c320d1c7d338d73d802ac51bea669dc8054a6c2e622dc65aa86908335011840f3aad273b335c8c6daf5878049621f9b28e8d02a1a381d5f8eb2428ff7d7ff1b45f194b46ce470614267d80891114027d9a776d207a881c70deff878ddd3df565b2b42ef990f16ff9bf9ab625b05c348c4c5970acfc09c58978bb17ab162205f95a9290d1750bb2deb7b45a093f4f554d888afdd081d3a0dddd08c81ef87a22559b634f6ea205ecace789c75e0539b4c9e67cda72bf200f619b5b6bd7a346634c56b4668c0ad1d006f3cfaa2d98cb3d9f0913ba48d6252cab0174e50f23047e2194bfc3f3946baabe6bbbb06a30d79b0ed081d6f2f948744f7e7048103acc40c74474d1b72afa1edef43ea450a40fb98cf5446fe877fb5acb207f6dc694687f6f41c8f2674fa2ab225f48d191953be074233191f235c2b2ee22d35d386124001b340f356eccd1b64fb9698719452ab455f0705ea704f7b141758950298d8f2b314fd3b888540b1c1c647649fd72130712591c41dc1df449f547b653dee0be077c68a5119eb3ebcbe6fc64ededc8e71a53d0abbc20c18656dbb2dbd019403bce16ed60a9fcfad37c862a7cce5eefb24b3d7ca427e30044647ab182e963895438a8d38ec52ddb8892c6f5421f10c22085ea69fef0ae20978d397c3f43749cb50a2600df7b65ab625002231b780ae8530ef845e82ed6b478386025d086eab0f815bc7604a4bf8d4f6823cb77e07adb07dd521fbe1f7595fcf6df65d1f7f49a000142b26a1d453af56176a46a8432354560df5857973e9346f65f4e73eefaf49ef4aa1f9d7f6b0f90cb3418f3ad9bcf72ce0db5050092206192be8e4cc65f022439f0b24babc8120fac7706ef06936e3995e3a5e91bddd651745b607ba70f5b74bca33dc4da95ec13db31d4471a39d2780b672a3b39cf8b793d55ca0e2e8ac7e186c3390329464ab962a79db7d3b7c6357c2a1341f36b1bb775c51ebe65d8d4ad3cc2befc9793a972695f2a5377ef3dd4514d326303f476677701ece505dabbf5a0567f74dab81669c947134012b510e85ecc52d23ed83f7ad02a38eb5456a1975e661ea4e34b38baff742b372d0f96bddc48cea3972bb3b6882b59e5bfc299ba1579dd8c0fd198590f452d8c124e5e7d924035c3a5c97ce3c9c2b85b69d05f4f402e285484860c950cc412ccc10f247a3cc00ed011feacd1571479fd879b220cc0a7fe1b1a83c455bf43a291e08ac10bce0a730dabff4e438852647cfd460a8800eb9e20e8e587f85a571a017611b64a3f000e1f2aec1211641cf7923a0c830e7948c50dac4378700fc2604039ec1bd76e59b0804a7fbb7f75b8ab72ba767d06e6e5a933e47f1a9ea56b5f6f8c6eb9f95426c8af7a38c3f3d759fed921790057fd745b597440b4006ab555b141e37b06054dc9bf55de4d3170f6b320415a5da4657fe1710da8f599d6fc7a0db742e77a96b34fc5efd571b104b8e34e416b5b51659058138d90c6c7894560d443d7545fcbea3e6edec823de8e0aab9d6739ec1fcc65f26fcb75eff18bfffbeb7a5d233367126789a6f4460315f73463b3e982cadef2fe0313350ff2c18a1f15627e2013242163edec0cc7c963eb25c81ae4873622f8c697ab8ec9d1c88bf4e7e27d9bd0c03e95db2e6dd301de4105993253767b3cfd35cf7cbc59d1fca421773bf43942f25c1f0f78a3e9eb0209f0e97961f7551243dc755db399058d22bdcc310f9b302c4c9b994b88fdd6d62cc490e1da9e2f72a3aa393d7b5eecd66ecc4bb7b9246ec903431e64d77f1d6874451fe6ab03bec44b65b6da8c1fcc83cb8d06329b5b4f69a907ca083d13e46ece8e33cf34167746e216be678a3db81e6be516f46a3b3c5d3ccf5a2ab5297dbf4182ec080f58100dad259f43efc4d7af80c4c31307482a57a02273592511a81cbe6f04b9e115d9bf0f240eacb51db97c67d4b38ec6a92efda3be9f2ac139500dac91d2fabfbad423fd11205676cf05aa0b3c571818c009b3a249b97bc54e03e3e37749ee32a1e7c60030f91f068f69dab866ee932cf8e1ee28248a4aaf0775a38e66fa6d5449f00312e645928ce7caf07c0340f48090563a3e911f3171da058679e8ff15f18fb18b82614c5f4a6d68fccb105e8edf2b37b95e54100eac7ee0bf2dc7bace92e044a1f360a821950e6d823aae217ffac847fc6c689e7a87a85cc3f64cce9e874fda4e129048fbc95a857e87a5750685fe02216081ecdb17d7eb31311d8741d0f52151ec6598941713603ca70f3e49967c60d831cadc628313a523f6577398d7b571537aef88be0c64b4b88d76014851d894129c395820c63f392594945b8117811da62300c6527f1b24ae4fa2f7d99325ba0390eeb8e0b41b0abead36af4810bc6ae398ed11e40ad77d4574c6348a3e06a0873cbe9b7a3a7634faf1ab16a3e5f0a0d66ed118a2888485bd9e62494d8d4e6e843216c58983f4388980152d36d9b62272665c29812af0c954082d02b26a0121644795611166f50025235e196709f69db0f8ff7c0a8745bc1989a8d165f31bd9053463a5ac3a1167fdea269bea83f48fd847ec49ed70075df8ba51f8f86aed73130f7f871e93820d16a66a7c30b5682143ac5d61f19e2e82e4a97642dc3f406c8250103f000e9f96bdf94a53d6ef5232f4675ef92e5a3c3fc9078736e28cf96fa7e0cb1500fa9c0b0661cbf87626b6b730cc83e881d256141cb864e87a6e340de6c9889a6347fa6598199ba3b004895a6ae839e50cd4cd6079563f6f77ac74412dfc4889647d41dc116a1cd4b71dbe4ff349d1268d7fb48880de1905def801a1ca93fc3811776ff004b82e65ada88af4f09d9a6a0e14d8f93c81d41ced2ee47693b564608b3063f016b3de45391c4fba444ceefb39504d12644013d1300667be47079ade6ed8972ce7bd0bfccec122beb76f8368d9302234c94fa0a4a1d1df6b0b67cca8b5a915bdf0f5aab855f8ef6d2ad211a09a6965d786648d287e0fef2c963d4e64cec77b8d8ad0e17460e76a4f89be3705c74662b57d8e494f7867e16999e24870b38b97541b7ee85bdffc2575bba3488a417698974516e59b0de1c6b8cce47d171abf6f29b8eb1a79ff7195cf693b3795c856ea19c3262177a2afba58e0dbb1b0ee93e8fbd3bb8ddcb09fbfc6445c6b72260845b6d9efb431bedcd19ab9a6176fade87361aee3c0bdabe560a9d9ad0a173d2f7f982d7505a3f73c27dba43910dad7189e46891cde5144c40d6117a3a5d5c87bc4884611c9a0f206daeff77d511cfe5beb7ba40b05bfce77c42e335d54dae556090a280e68e9a6e4e3863bb83f088ce50b1969a51a0e8f3c1bdc906600cc85fbdffe7efd81867bc04cb81ffd740180a0fc262b3c0c60fe22cb95b103dffd0c5789ed6414aeade774905cdba7add5d11135680258ca342d474455426f199e74d9cfe674a0a567ed75151e8c4e87c07f0c11b967a2ece86230fbb2bcc084b19b93115b4a3f4cd7b56e877fedd8f7900fa05b83b804c877c3dee2befcaf7607742693140722df37aadb4a81747724a5848739550116603ad160c386c45a984b0a519d6f2afcfc50371b733986b0d482400aab2c5a3a7d9dee7c8fde856bc9c15e5445def19af5b99b02224bef3a82e76b4d5cf8f60d2a5c06d2f5319d6572fb74a59fdde6ba4b6dc660c1351b80c62c454b1c86691800e7158d62a78a2963331f82331acc59c25167837e224feb61290962673a21040f49c9c4ecd08adf9ee50081de7d4257a48c0f1fdebff05d16b5fee058a620f6d9a6f79c1e4957e89160b4659304b443251be124879755101c20b6eddd35dd28276c1bda88cf514e9a1fd7a78f7a5f29b562f98092dd33294e1f7ae292d851a066e8825a9eb6f5407f2be9ff643d6f9e61246abe81e5a7ca5885101f173ad3a12e7fa1bcea2d14f5d0426d0e97a169f02f796330e4f1e899bde15d444205b9d2d31b327fd057a3bc74441a4619c0ac66aa1a4d131a7bb5ab534f4e1f9ec78609cbe39633a9a9adc2ecddad091d2dd0b1c92c4f3e7880a9df083ab19c0259e30da54c27fe61b323f3bb829068f38b1b50f5c4cdff448d0a96dc329220441aca7e0d0bc34a89c51ae78d40a0ed83ba61e5c473c8cc6231d65d7526020dd47df8e3d87794dada0d412331678245ac2151cc85c371b61d2a371759c8aacf5fe3f35bcd32ca1cf5c0882f90cb0ac8576426e0da94f3f839c95369b3db4852899bfacc387ebfad194ec1dd897b1a745ca26128b386b3d5ff1c9e9a37f6de7d9b9407facf6a5d8f108a605ca4b0bbd28543dfde5234b8df711f3df7fce0db654b69c0c64b2e0fe200e8f0cf7a059372c247db39a0868dca487382d77d92669f7d14d04d9da100c37f0325893081b72e0557584f82b99d3992591a4ccf13f6ee9bb9ffbcddbe7a1d4d41b1f20d5c292bc15f4dd0888db348fa2181ff0a945459870f5fde1a2a74cd4b70116bc8d7222dc5ea13cc206c84cd9c08d65524b3802b15791a45e9f0f2b292bbcbb9f1b04fc77c73c7b3ee0675355f6d84778bdbd67a98c6017afc31d022d48c2a2cc97cc5829f428736e0d620c6763137a1ad0335c35e0d350ae346518d657c76d82c3cf1142874dfedd18be97373a53e3c7f7de732405c1fff726cd0c28ab3c5edfc0d20696fa01fd0a3625180f0239bbfdfbc8e6e0e1f1b41dc9b3644a272e8b1d8976eeaa162f8fc1c2bb9f80d0fee3e1a33f6c9ff92932f6cce73996eae1ffa7980ae814115b7e8971afb8d7ed0889ba6fe6be897d79c02e0961124f08ce61893db799aa0313d0cd967da7e0b691bab8bf8e825a76aa5f877fbe8d63e8971594946a703ec7d306fc0b90f758a98eeb3af9268111c65063578dc7968631cd512ce29e7e65e8aacf25b8e873bfd860216e1efa97b18957b773af67b556f6ef8a8d95d06288a63de5040a453029c00817fc16a68829c37b8a78ee3486d2fa83da4fbd07eadbf2ab0c85812e2f2f08c8180c1e2d1870c2742e81f769339b889269ab658cd9b45827e1b29dd8fa0404c9aed12e25c6f8fdd3bcdd50d679b44743b3f34a5fdda148d0e3b2e95c37fcdbfa9a7bde79a04ce54e0805ab087b9ad63eaf11d80e5f5768971cbcb45befe6a9f4e46aa8f1fbd4461443f60753218a5ba32446756b8713b3152343ebde35e5557583f4a0706921f9a20c958a9024759636000d207d3fc46cf961817ecfa38e72f79880354770cf71144699316bb2cac336a304369a88ea0fa76e7adec41e4b0ba73811f87b720e6484f638145dddf9a0c68a8fa2d2812978fa1c0f903deb5013b40b1f35fe4be34c6725df3a421889863bf897c7260172543db9a1f5ed61b3281eac87dbf69c5f138ee8be358e51c28cd6683316776671c4034fbd7f0239b1222458b58a25223d27dd4a78c8a93b275acfbec6471716fe10f266c964902c744cd6b087f10bda2c960a66d4ace41d0d9c8d5721480b7a66b9059944a79e0ee1264500dda53e7e566b7cf56e6a68354fb7aa662235383983786155a8a1028f2f6959cc1f8e8fdf18be6757921d1dda8999babfad35acb75716b791c2bc215f5d36325fc850e767dd97503d60162cc6e418b04e7d12dc3d4e36f695e7a88a6c282c880b5f7d7f58771de37e9652950742da4da33cf88efcea7f95dc3bfe50bfa4c42f1d7b8e5c481a8e17b54703a775b57357e66e64ef5edb529e1d287ea60d185539d9a98f1c93bd1b1466a2ccb84023819ea348c523db580eddf57adc18478bf3c024f404db17af8f43bbeaeb00a16c3fcc67c2e174acb96d9cfa35e218a870099863fdd02f0f7b8db12be3285d9bd2d946481950c7d66371faa4a11ce44964defa55aeca609d354494def2cf867c642444e2bd6049c1b196e8b32e90a5ddbd4f5329437df0aed2b3287a96eabd51d83f421b081d17e1cb11567f8fb9ce1a4b2c279df553f4bd7fdb267be33ce384c14d6f010ca0e0e52b32f2209c5b596438e78aea2b018cc4f606cbbbabd52b5d1fdfc012c108c4c5fe8a661a11482f3c2d4872a7ddb58b8982712bb084d7c0e7e70b3c525099737667841c2d18fbb07ba54a6f9f446bc38fb2c3654e872a9e07f14b95409e669afd80bef828d4efddf2cf571ffb6e14c79ad380dfea627859d8c74feacc4ace2e43813d6814089b67fb5e14f11a67a8b94d02965fe2b31e6f1c623c2edbccdc3ad77beba0efacee9f4f7b4b0975f271b1c20bb80a91b30adb3641cd596e1409a0ab2d7fc1048d7550c4a0a8e3db074ca96df389d614fd6cd959455c8cd5dad8b636b445e29a3a1a33b14014968724babab4f300169c7944b86c7fa0efaf8f685853111f71bbede1c00c42083e1326f8e98f48989f44d5fa32cba3b78eab81ecbdeeebdffbeb668c081cc10b81fc044e6cc24716191c8c9a564c22575205edc9e8fb0fd43adcaf588fbbf1018227787e0ef1733d7a3fbb3833896a26ff7abdf2705a1d38fdf2888d4beac86dff3bbf9a5943a633a54fc1c871597e37608a563aa4f26a3e8e3d315f232960dcdf917a063f9dae5263535ce1acd8463a0b18f97b51604cf63aebe8d94cc5a499590c33a96e286e4350501caa86b91e3ebdb66615238feb194e3ede56e8672460a2448771b5328ec2d05900893c1e7e143eaf1142cf8f1511776aa6f3d35eceb6411efeaed94e16aa04534fecd1ab1d933dcfa6386d10d94a32e4e03988d554b40076d5105ccbe3f82ca6e5ad71b15c84ae9ff9574308b475171c465be9c5bade01d033ed53979abb0e56ac28acffcad4e3839fe009b266a2ec3cedc3a44d6e319c27d8a8328706856f7a0ae69d05ba65c669db170be1441031aefecb052cff2fb6ba3a20309bf12772156e01021b5ce06e6eaade9783922dfde3d69208db974eda5533e14368bc133c7bb34b4ed17355caa161d0eb00b7f8951f7ece887c3ef90698833e2bd60100fc4e50a207823046aa4f099b833fdf01fbc201bebddde39f3d25bae23677ebfec97e01c39baae4413fe1127007ba3b9565eaf5845313f0a2ef78d60a49237c0e0740afc2aff546d9bef1ff6c89378031483dc12c2561be7c37fe487c28357345eb2d2ef573bc2649e6bda418a421a88d5392b433454ea2e437e54eec53a06a887461fe3f0240f36718af6c3dec0c5f44d5f601adf5fcf0fe567e9cd08da52211ab4e12d1c74e6aa10264717583264a51d0f1b6c2659ceaab6922ff1863aaab6465c6fcd2e710d304575dedb6d3a828a5d3b40a3c6d5b1f2b0d4198ab80c881660b373064d4069ddacf0f028b775ee5d3cc88aa6fac662eca2a09b0c852861c94c4a286d97cd76ba3815d8fa75705ce1c9f7fa5a3ecbaf2d1f1d5c073efb540097b7a9da6bff8b7c41c447241c76ce8445f6b365fd82aa5e8eca11afa79b283f1bc75a184df0e3364ccd753a111caeee967c12c26e1a6e0c7d8cee8e4f5d22f8e50d3a5d31a33983babac89aedd14c0296e1009249818bb9da1660c6aecd91a16d85f1e77cda94fb7a40bd3b772d2396360e7a59a31678476953cd55bbb979472a88b978d6addf10b7951bc52013ffe064e4a4b0b147aa2c4dc189abda50454db967f932d29699cca82fd5a8ce5895e3a5ee884bd595b1573e26f231b6b46900dadb180939d7c2937dbbea5427dd7495940fccd8d43b80f4c43d1768c4f83e6daffc88f8e03ddb1c4007f0221f1aec116d430050fc0da1a7f554dd3f9f7ff5e6b43bdb67bcea298de93593ff794f235cab71e52e4538e27f63e22bd30e1b01d919ce8d426fdfe4786f4fdc03c1315c98d3e76ca6884d5ddf77bff24f22300cd6673e132c6b352ab042530a8367164a6e04d3099fca110ca111cb48acc910a0602acad9aef58bbf9fc0a994f45e0498c46d54742ad38ec17a9d3a4130a4f893a75b3b90dd9b530372367d932816209a46722bfd5c9b644af49ec43e92a76770eec673dac24b975ef8fdc96f895231d525de8bf32c89de80c12472440757a848642c9946d79af4fe4ddd14a48c4bf3f77afdd4c7251d383a6aa5a61ca2621facdf935a01e3799cc00407d402efafb762f5afce6b8e8de7dd4e18fa40d8a7c77aae9bf3809d131bb8972207a5ff40abe23564b39a4c8e723128e3150df083f2173c90bbd421f00e184d53dbc1a377d448350efdb6dae8e465890a1a56636cd2ebaf68c85c7ebef890b5a2dcfa1ed9d1b7d401be4a00500990121de09b5341dfd02703cad2686909a155cb08953393ab01feb7f2b773006d3fbd74882c476cc304a251a4433bd17db4193e264ba91909975d43280c3b9fd618b71f7a60bd5a81d629034c6193e3a5b9aba54dc1414998bfc078d9dcc9e5878307d85b23b3fc780042e8788af5f542d1fa2b3af5f02d6f462bb88c66b510ee19e4c279b9c4d41b25ee6d6aec5a4ba42c022accb2e6d64d7c33a9bb22e36a4a23b1b9a6e6b27be98a5c54198f2b78c0d39091ed75dec5debb0db6f9f55b9fad45b8abe67734947f646750e5a93467e0d94a9814e7333b598e93f5018ecc670773390f4f580fefe4d20332ab020b823706e9ffa7e31ffdcec4aa1943fadb607e94b29213b9029553484fb00ceb535f901e9408c4d3bdc121ca12e4c1f241b6d7362b73f629fc10b770c3d6a634c7f032d2385a2616ed5f9e0dad2f36ece6ac322eff14981c81b8177eafe1fed6897bb4901bef4ff2279bae23e98c0b059793146b6f366f1a986106f86d5c4fab661fa2cf9df42fafa23d6cf72d82cca9a4d06b532c5faccda0b9de9ebee3a512090ac5fa3b462579537193022172e2e4beb716bc7cf14525b455de3cd619f7262e0f24ad1f6fc6f4c1a5969efe2142bac2d19d27274a925e2f81a31ddf7f963b4b3dd5a027fabb408b83c48087fa2080e059bba076ff1d601f6f1c30b56b650ad76eee4f226a20a0579040a2037c523c1b96de5d1c83ae8abbdfc2778d363987737ea8249abb81cb1b988f043ae0b644514eca6f28bcf79c53b71a469b8df176c5912f389ec57399a71701ac750d54dbb1d2588f3bd560e763705e19d5a0a1ed56428deb2c2c6f4cecd87fbd0694b753c814cb9dd07c8ba61052eb4dd0da8748a9c8693edc90d1e77fa7e5ad4965bf1933590cb401a4af0e178507bd30cca6fb8f0ffc672ccf52e21b0a95390dfdd0cbf06839397a663bb4c7b62a47ea40aed53801d1ba36570a26e0a53cd7d69bd2d925448400e2d5f555e4af5a7fb5a05d6506421024832241e0d274ef105d641728a6f7e911996208b887c6366ee7f0d62e9e7d1cbd6ff77435ca10d65e32b9ebbf4b5dfdb757af160444e91503416d8474161658fd111a6fc3c694da7c463fb93624c1d5514742964b3c6eeec31129f1cd38c578bd09ad786c8721cd4c54e7f377b759cf2275babe2b92c2a3b23de2e98a96f382377b4f3afa7e455d25820a0009b99a59ccee1ca183a6cdb23f2d987080bda05a69f7afbc4cb15fc861a57730c0197590e9344320f6408e5ede7c088330beff4c5c0622463120d9cb5706b5d407fd6ecb70cfbdb36cf05397fe0d48716e1707cc37e149a5b96164e273344d8b792fa8d80a01462ec34e869f272c8e0fd029e034a8a3ebfa4f7f1cce9060aad499ebb8e8fc55fc1e5794903de046fada54f89ebaa6f8c09577467d3ed6890b2e6b61b8af49017fc41bfd3d675e40ac8cceb28c406a3d5e6da11ff274143467ebf8428ca9366c86d89a6713b45149fbba3d068e8a2c02940b313f4ab127d1a5ba18f7b2c02cd7f98d0a951b36595f41b825e7e13d447ec2dd07f1a17b0381c7c5fb37ab5451c30573101a6bf4dd869ca650c22f229e4b6ff678de3141bb39800295719684b496283ec2836460e8a8095cd01ccb9e0a37a85fe54946e65180adcb36f50158b3d63614019ad657119e8aa2b9cb7aa85b38be3ad6ed930da866fab5f275e8fdb43312cd2694a4e1446d94b15d3002355c1137f8354522fbd8f52d9f9f0a9d9eafffd017743b3acce4c7e8b61d6652a6b95bf199ca227a4f524b29dd103511f343aed7c7cfe64b701432fb794a8cbd410b389237e3cd567f85ad1e5eecc18f18ad6269639c79aac55ebfcf74e046b6c473efb71269d9d43e38a53abfcaa03260161fa8e55216882f1013e0b98d700e132f0a9fc1a397b0a2038062af03ce4fa63d0a6fe2bedeb428a99da014c14ccd6ac626fbb9c27cdcb6f8f7467ab77eb59bf08a3adba0ff89f75d79e006d377a910e31a36bc114da243122ab2135bf515cc258681e95d38693d44f464d255ae290d664e3e1b794d0ce607a906e57b508b3f42a04611e06f80862fd5a41c6c1d1b51c7cd329d7b65613e79eac6e25d1ba3548f9f975f2758bd723c1b8f6f9eebab240716b92f154cde6419f006ca9e243e6f84aa5c23ed876b6b480586bcc871cdb81494b1e6135ed9f75876b30a00816c41b4269955f28b7b164705cd7f2ef533d1fc1169f70d8d24f2a7e06b8f5fe02cc1a244b8afef0e9deb2393888f69910f96826202ac70b1aefa59fcd6661236a2244da18bade88c4ce4468d831a20ae2bba9c38a58a6fa7be8f42b21320d47c6bd1685c0bc7b4d12349b08d6d93d09c2dc6e71dec8e911fe1b64cc75d5c76c4ccf40ebf24d258f9b751f5ec3bdd39551d75f80ef28e105f883354475802fbb2479739c3f6852def2903ac15feaef477bb288449c408fbd5b8d97e463966efbacb6d961f5e7d4a16ccf40165e6d3d197380e3a385efdb2d99c7cb018e527220efdcdd4fe08877ad5906215d484894f43934ae178880662dcc46cfee7896ebfa42975c27d578649439aa45cdea98a02619b114216b9e8b7b717f25a377ddab9d0f93a78038141486297c817723cb5774b10184b53c32129915b420589d48b82cfaec9fdb5f610c289b088775f8a77f50ff59a8eb1a9b7a5a6777476aa957bcabe17c99647b25ad5a37eed00c845480e5d038b43ce79129d99a961fc575ad9c88eff477fd1723b6a340d3806c8569c6947256a6461f2273123235b78912091037d4005861fec1556e14b082b247eadb8c3a34f7aaf8a7d278164c6576ef7b4107fc0d2db6fa457fffbdbbef554884f20c78ad01c38030262a92afe5fc2d3d652f40796b27ff668ffbced4e9063566a3de99be8d852b85337bf3897ef3cda2c04d93e02bd4dfea20ce4fc5065878f3f9fe75a2253559df1a84f7b4c378408d8fd93261075c25faecea3c194f3c2d75b6641782bd2f3bf4c863f9c48c3033caecfd1695f7b29cb82424ec0a91f72eacc25864f240262eb6c297b92943b35065806d4513a9d4e64c372645abd6bb1aa437a5d3f499999ca865210943bdcb0628a5b9dc64273b39616add778a578c3107da52d0640c3c7b19ea4d74419ef0d16b1c56ece1a18dcbd7f85079460b5f34450db0df6724bb3cea258fda22a3c828f153d887ea574a37b6fa51dd197b921ba1cb86ab0f71cf2dc165b9229096801df32d6a4272c43baa63aecde1205e81f52a07c7de693c77404adf18e983978b1eff7be7b738378a2b77530589ebb325032989764be13e2a3235eb03b33d25e0af5696cd28a5bb307d5d300dfaeff79712e3386bbe69a05465d0b346d043ef2c75b10115685a9d5b9a7bc80f508fed880c78bb2b7504e3330267bed7617c9723ac100f54e9626d53213c3c1a0d5dbdc95dd18929faf5c8ece1cf00222c1d61e38731a16dc7d121d471c68cf3d0185b15d55ac8e8993c78b5e6fea664f28d9df81c438902c9d8a4d554684199526ddbb00d4b648d7990b1a394a1239d63bcc0539ad6e3f921dc1301737344f962cbbd5790c8ce0970d29ff80253551536f6d9edce56d8011bf696594bdac32d039e1e789a66d14c13e025359417012e10036e4965b751133e2109aaff11721bd23714e7cd900bd7a4a752bcad6a356d522b1c8859e17335f9f30e70bb1f62708c081d7ffcd523bf8b1b54383833658afc27ad9022fd68cc95b710fb4e0c54b8d83e8ef17c35d8494867960e4edb4c8a6dcc9a91814235914b3603169bbd8e44ad46df71449df0386f700765fbc237d986184a05201b5bc95250ae64b5dddb2699a11c0e7ab5874ab62df8847fe11eda3303be72839b81f7651d4bc16cf297cbde7131be88ce1e1c10ff31c2521263d1207d4bcc2e44af28b8c90e7d8599092188ae0928549e0b5d66c66de4283b58ae8c0f36386b8a2a2b7880a1f91d86d80562b258c757ca2cbd7e45831750d10a923e5e9bee1373d11bc4e279af97be6422534a44857055f51259ca59e2f3af513119ae57fbb7cb12c132829f61ee57e60f5bc44865c699fca8ab99968daf612ae9e37b17211a33fe06df5952bdedbb0d6504ff203d6af600e57f40bd41510f110e21c50ab9833008ae618e9b07827748854f09c3f897773b4de36545193ce238e33f6243abbbc69dbaa638e4c6f085447c43e417b178c49dd1dfca376ff902a5cdd198cf01dfe76b705c6179bb1dfec4ff544388c449c69d11c99f02dac2913d3a86a1f43ef1e3b68b8882b298f42b0bf68a6bd1feddd859900fa8aea1f34fcd124a993e9f54fdc96c23950a98e3cc4a35d9a7da05a455b0aa6f8925d6d05d35b82e99ce2ce928a554cf67467392563a7778c9e4cc7b1aa67182aa3792b59539df089465f6e9d41bdde6306196f34a2a4c6bf53c4daee7a83a6436ca202437236dd759316ba62b1e1cfe291a87b20b36e5bd535a7d4ab7a3f7aea89b6e0ae1b090d0af35715a64804e253648f49712b986ec500cff863f86b0619c2b5793f353d5e3f56804a7949d2dfc64ee5959e2e637a79265994219fafacb15234afbbe022f7414cc2c826eab725d414279ff6eea3adcec6e4ba7fdc5c24d35a7fab7ab2fa5cd487b6b2e2d279b627801d77e89e17ef26d7dc63fb01305ad622b4c73dd54c9e1aa869c850b3d1ea878244c43b797b99f4d3365d1379a193574443e4e54876f9376c72c1df927e4cc287eaa50eae5b8ed4cc75e04eedb5b110e6dfe4d9a780840c83379fd01bea659717703d877d0a3112de4a15b7bc5ff0199d61c1d320a02bbb9bc0532fa0d408380382099d93d1a2fc7153a3041d908f394ee65d509c5f0b637df4b91482813f4ece8c4b6b6e4d0dda9828f311b0eb3600abfed873e89676a0673f9a790b7825693722c046996a734af10ce91dedb40a4fcbe6223b5b8751f89f9a96fc56b0791de38b59bbf4c1ba735a5eda353b0be521a0e980fbfccb6fb506104eebf3833236f54e4db1f11cd56eeba70226e06302fd74b50381b87d5680107c90f043ec45923e7552bd7cb6e84dfc4fd1b53b4ca2fc42aeb6be4a7d5727683474bf0f3713fefe4e76144849cb1d53f31d54acef5e53a5c394573e7c8d243595f1cfcea166cd16466b75adbe958801d77e59516ab660914dfaeafb42adadbeb34d526cbd80a1e3058418754a7824e7853a199854f5a3ca1ec2f514c6aebb82e8e4a134f10108449965c5d312ec7e3d3afe3dea35a898ecfd635f0ee6308d06035ea9878848b64714efe14ae1dfa564507bc0f602120e7ad85c8530735e3b0a755f7f2cfce26160645d62247e5ebde1a79a71901a63db3ae88c7ae6fac30c2f7b07761d0023da1b48a2063a42d56aea7571e5c3ea732bf06647c8737bfaf73b5db1472e1a7456cdf221ac0a3d8ddadd5aca611e491199d5ed2fcb80273e18e6c7469fa8570522c686dd86b40e44eb693f6fdae628ddd9c19002b9663efcd80ce8b4031e5d5e1546c140b7a58f3cfcdf4a55cb8400bc38d9bd2daa57307b1c91bcf9ed3f1252f7271fd534ea5dddc52ef9af672e62ac44078024886ddd5caa02547d30923b2aa4c6118a00f5c3a8d244dcb8c2909a6dc6b9b93d544a9ed8baa66cefcbb20ee0881d6c502908615db563fdba61bcd4141e87df53c156dd9dc700671a882e2a0331342055a28787b160bf6554d94dd56e439afbf6b1230249db53349c096b341145fa53ce18bf2111ead093cab1f8096ef718aefc8d979496ce7c7570796f11b546251d6637f3cacc609f4842c7805c09c0a540cea99ff3b0e54ed9aea515ee8925300c3ffc78a06e404363c90be63b683f59665b9f043311136589903408a348603be59f719bf863e505a0406e9b69bc50700aed2f4c070cd6b9f8198ecaff421a6a7e5651a7432aaa9bbb9de090f857bf3f72deb74a9332b0f45589369c72ff505c2b3330b2c38c973dcb71776648ca27377587939e8e014ae35b79845ff376b57b9467849318204464d3439705c21b048b8d1f9b9a17da402248329359d886639bd0b635e3ac1449836f80f8d3c27dc009f1bba19dc4c0ddd7d8d8ec7a5bcf69921a428cfc6b5828ece71565a129271df21a9ffb52881791465753878f6026bdf5079a4dcab08523a3dac37a5a55919508cac98b391f040e24338c84a002edb3a942001728bcbe25970c69b565eb9b9fa5d94b7c828f79e591e2c11895db7233573dad42acda147d4ba744790b3c6cbf9910f14e3adbbb2ce45ca4e2924bff1d28e627a3876d572298021cc37963df59e21c4e138150754048bcc66660b3ad0323cee74a644cd3eb906586f8e2041ca539a4e2ea4f445ac30194f6bcd50e50e305cea5219c4a35ce9601577261c1c2a060395d5d51740c493f411b87dfa25710bab16af076bc2e6f05308d8031fe3471a7456ca2e21ef17febe1a81d93f79619d6ada7d38d559f393f9e9abc6300a6045e705048272bcb891d225adc07dd06b14ec19d1bb4ee5e9e5d08cbe1a5a2ef1ef1e7e6590b94e60f83b1f383be825a39efa8c8e01e98bfe09eae5f6433539aec6b988d9ea2fe6e6f3a20571be2515a7b7165cf545cd2ec80182bc17eee372723a733124df00dc0aba3e9120e69c3bbcbb40a52fe42ba293dd3b1903b4c78eb47c7fa1e7f588cf4d49e7a87e558ff872fda0a1c4950f244b1efb0653a7e99bea11983d7378933ea9386aaf9bb140ab8966be3d046378ac18f9a9baf1e50ddbf2bc6d8c4da66fde394d29a69db581afeb341125f13aaab617e8b2559cef3128e7f60af66f26847b14be1f430fb8692a755156d56cc87fdaa4d16503414fdba52227591590e1fbecfaed70b603c1dc2ff9c3f4071dbef6e5e8092b5e7d10963ad2d3fa655fd4d2257f2b16ab6e00354c13e4dc1ad6ca18c8b16f647bdad83f01258e6197c7c7caf930857203e98a0df3ec3eccd938e7649c92e6a8ae2f3f0e32db54d8567f7a07d4e16bba18784385030cdb8105d18fe82acc013eddb66aeb783673b447609e5a983584786db5e55653864ad87791747101d2f2a729181058e64c8504ef3d1957c01138d6d60af7b2e3bf8c50356140d9a53e4adfd41e4cc63c16dbbe4b8877d0659bd710169c55179a797bc865e0e269a956843dff587de8a9147d14771089037a6b5f5a235f497f39985142ffc4e64ec41f940b4a7c1ba7c6fd0ca7661803f7bccb87651a70ca377443b536646cb6f672df0da73d52e0ff6d816cddb8fb52f8b6264a598aeec84ccbc4b957f54b7584263f54eda9236169843fe5227ac9c20b14acf8f47f978e7f219aeb89783976d306b840185a386cdc6cfe6db65c5fa6d6777adb33db420dfcc210d2ba4b7de803e51ce4b0d09cfef6b12e528fc1db8709afd3dfefe9aff88eff479393d4908e5db7913792336921f2e2b928c36b1d469d73979ead411e79b0002ce76a84e96729735faf0c47a8a6081a658f6ee11d4dfd47ca4b56c3d08516ed768ce47f45b1f52ceab717cc89ee141c378e42e8b1fc2784f3a916eeab8200429120cf850f88fe1798edeb8566bfc4bd437adb6b21fcff77ea4c48b26060384abb625525586f0a6d4140a1bef0d68a996a4cea6dc61e8f963cce3f8436dafea77d1ea88589664ffc58a5b709bd40a0a8be6c40a98165180acccdcee775be994f307f96e014617b5291b7cacc663126e9c5c3f46354fc673ce0ea8057835fe324b6a8935704545bfb8df3f63ade384b17ba6fd89f39f32419068ee6425034dd263cd635be6df286d48d17f03cbb08b199f863ada1341fba227dc3d2a54dd36b43528283ce6c02532f7e4675e550f1e3386ee035edec7f894ff215fd242bcbf8c39671da89f5cabc63f5d91e6447a369df902161cf2a152434b5828a4193924f7e769734c01a82902b2ee1f9c14eb706c941f106b03550de9f4448c3677825e93339bc2d760ceb729f11da781dc259349984471f89eefdb392c6a033fc35d88aa135e1c56524dcf6b2476fb2d2f43d68201da8279d504d3080011ede9636df206e73e62797c9e089a7b4d61f1ae1e01ab909044c55c416bfc03a6b2a0029883d0d1b95b3c5bfc929bab06da468b39277df6e0aae20c930476da9b11fdbcd892c30a207bd253f139118b19f629963bf025e7412858f650dc9bc49590cf2654c0a9a366be44d46b65b8e34c0c60dec458f9e649c8e3477e94051553f021831ddc99396f3b57158ec8757fb73a2054fd8850eb5e065866df20426dc6acbeb0c9724d3e8420738e267bbcb6dabb178f5e1674c37c343887ae6996fe9cebf75d147c9d3a2e79a0b597b6c2f7491b58d64ee13b03c2e31ad7339bae7989a1405d222e98a0e709d6236c578b0a094b732854ae9549b961957900b122233124ba162b4575666902d255ff4d4a29850339a64bace9fbe4d437c785bf081944c1b72121662d919c0edaf53da059c99057d125482b0eaa47aec730a8babdb6ee7d846bc69780a7207dc092d4c5655eeed983c507e8ce0317805661c553a0d6b7ab6876171bb4540309f67eaa0b5790a5aa07550e6676231e24ca94a29e7d6a3423a651fb311fc6a88b5d09e2b082e73ee5f3fa273095be005b23d0a58ed2ec527ac5195224c71be45aa2a9f5c8a77d01f4c6825273c0015de09a2972ffab892ad2653594f6ce02589109e35d05a1c8692e1182ef6f1df61f869c21d8467212f615a975a120b5dd1ac69c103301e81cc90b82d0611cde5bea725659bf6d6d03e828204a4c446a9cc954d53111e706606bab5c24ff228b239c9ca7ed083ce94bdf9fd38a064882492c4f09679804d5c6eebf683b53df6ddf0d64388c03da37edce95159c693e2ac950d7d250c602bf1e16f5b3203157b3e5e01a8f4352c180afb28a9176015112d4ac71c0ae9799d8f1641ee0492b09fa2ceefbad5fd6a46802d728a36380bd2ad79f26c977f1832cff199c875ad9974758a4338898b3cd147acd8a53f1ec914dd89bd4edc97221c3883717fef767860fdda471aa72420ea132d4db97b8f2b0d7842a74679514081be994f8422a01e25a77566a5d803415f5ea676ca3980bf300f3ad8cd2f1457eede857fc495c9f1398ce28743560c509016cfcbc123cfb0b744ef6a9c2680cad660d38642e5072eca8fbb14eee58cf2f491532e74eb634c4ba6cea3b5031099a00afa0cc99d15d28ee18f1acefcbc434c097337c14e1df2dd7cb792f7110790bce1c4c36bc1fc98753a52170f65272f92e0c98db4457b9b1e253b49edec175d674e4882282c848b5dca5a2c5e1c60e6e54ec40ee4d6ca44201b158c04f8fcb4f77d1d44934fdb8b3eeac9f6b2a48e7d6ecc37b94636a1acc4643d9c9a12753a2277d9076cc293769a6000fdfffa09c77afc58dd3d904e9eb2abb75e498c596a6e20b1b1fe05e2eb619b9ec254f678a553043028b9b19103ab6a5bd14e27fbb2ac2df42b47a055fdb721804318db8d28ed57c57f5f8935984d473ebc73b3afe204d57eb4f427e1d414c9bcac34c9e7fd136fdbf565327b149ebeed0a7829194231d6bdde4c772c8b0beb8e004ef32b0d9778a8a399b8247cb96784730d226c0644cb70d0cc84f85aa1d2f29b5de07f20518a17294a43003963585ccd27487af6def82c84e7b896cc77d0c12ff49c03453259a9b7e2c9b974a57fc003c4681ad2661412d8b1a2e1409bb8c1fefd4f0bc689a3dbb993c17e8d0c1017810eb95ceae6321ca068a6bc2827f260cadba48e329e4b119359a629ff5964701f30fc7ab18a7e9cfbec350f022efb74f126e178a8d5e565ec5b131c1b9e0bf043148d82f014e479b38cb46d9367990ac2dcc588d2e7c4d610c22fee9d8d894df9447e1a7745b14dd14ac4cf7acfecb1a1aa86c4bbc97efba81d6898262e6b8bc2cd696d8168002a7cc822cb55e478f5899988fdf4691a80556636be704d26467045cdf2d1b90c1cb2e3dd4d9ff52127706c358ae53b3bd6b834727ac20ec40677d300191930c1f59c21a45d2ba8c21358ea4fe0b1a9418aa63111e93c5706181e9e164b341444b5b7e14f5468942dbb509d4dfe121e11a5aa62fdea80cab2136192cbe322b174570f75bbebe467f030fa7adde3d4458aa1a7bb90d410180c3f805ad7a4a83b04493aee4c4495ccf0221e549ed551aafdb7500808dc35c35ad47a3a052eed5d8d5ac274b0c5935e8d157f166e3ddc85cf91d9850b6093cb12a7f08e67bb957e9201f0d0cc5dc33ce83e31a483bbdacb7ec925eba977c2444f4a2e01fc874260f32d49b2aa522bdb0fb8d313b1237b78e9562a6121a09082c3492898071f4bc5b4a9aaceb3c9dce7c65f8a5b54ce41814389ca77a5ebfc496e188cd52a471344f6049bbf41fec5b2acc8ee457f74a6b7c7df093cf3285e4e39285b0b1a5677266458a12a0a8df391eceb2ddb5ab57fb4324e2e6b3d469fa7941d56160f8c69c6f746bd6cdae2d35e43ce6e572b2d1e22369a2c0f0005ade41453d68743fd3ce682a10482d75334cfaf1a7b776043f5b435799a747a3f7f2d4abe38464cc042dce1e132a72ee40c25f0562cdfc46382d005ae867ac01190962eac52d5e16832fa59ed204dfec841b45e3a0c6743608a3553288bd2d33c0850097a90d8039434089fc7d29264c8c5275e87be5dbfbbf7de4d1e3c52db622353684d38bce98f2c5122b36e00dff8a8686ebbc72719383e87a03cdd69045fedf24d3181a68e8e2a1afde3d82e5e7cd380454ebf1e6a2cac6c9e0e6aa77fe500fb36137a21d2634cdc4e01d4ea8b61c683b2de07383f4979ec0167948428574b0c45728a1df785e8dc88df1ec075b0f87181044a5f4d4bb5ff57e1d4dd0bc165c21eb96da6b8119769b2bf4ea51e7aa2fd2fb366c44e5e912dd7eab44411903bfca5b6ec6b37a4433938cfe1bcbc8d0609fdabcb234d7aef1d9ed76eb3ed18a16731762ba501390b771d8d2bcb9515dbaff4eed18642645349eeea69d72d499b491a42c0e87b9ccaf42c19e5db1966e7136d9b7ccf2040795bba227815040151cdd0213d08b90b27bab8acc4f772b6d6902c7bebbffda6d4a74cbfbb5380d6c04aaa4e2a5abce7c825a94d55d36713def3f99c798c2ff54956ebe1c3bcbc630166cb6f682e885c4ea0f85646f2d1089106f2930b071d72784c21917791d1521d79f3bf61cfb30a082ea68637cd4b6a3afc2e6a681086f31a03a82820a7d2cbdb9e51831d41daa82145148b38975d65e5172ffab017c63d5729ae5d16320d41ba858b90f3d2221511c99ac87b643140c01aedb29f0b79de8b408555e37808c35d11ec86619c5617286f9c7a19b281ae39aa088ac6309b3cccd47a8daf459b0f99e4f0ca9e089f8330c895028744f733cf118bed538babf6b0c83c27adf632990f5457b14bea1e3ea53f875885beb6fd1a44036dcf11ec08c620a8d5daef2e624262e6ae1c382ee5f931f1f59984c4f7c5e127d4f428a6825e85e07163213a8b668f38ad9788ef30686adc9579bfcd8c2130e44f99de3342b61d2280f5689dbb77ab5f14aa86bc2562368ac57b5fd6e8dfdf72e4438f8ff63362896fd0ea54c648227229cae614ef1307e2f5bc3378d3025f1269dba151a84028d06ae8cfa0e67f9f651878d8ad6e155d59ff9f208b06870772374ddb350d192fcb2c3a0534fa03f70b4fade80b10f073e60940b6bfda5d8f81d45d0bad8fc0113d763fd76f6d720c22de3adc601869facb452add3951e6148f9229abaaee136e98faa1a608686047c603d7c95708c82158693ea6f0a8f88d6ab45e4a3b9c945101470f5560ae90032afc6edb11e05c59a59741fd67c9a87711a044ba2c423490b555795ea085ec32f5d2977f847c777cfd1254691c6ec34bda008e6d428b2b01c7208ee154ed3a7bc345394773d5d8529d9c509370ba72be7bf8cbc1bdbf05ffdd769d6c79d3aea078a829aae7bbcb85a9be191a0faa72578205177a9d930fc290dad2ef29f32b5cd48f507ee2c335ca0064edac90d566cb5487f30006a5186b7b27043d2b0e84c913c821f091062cf6679141b6d087414f5585056bf4e2f987e4fc3d5d1d3de0b98b51b09330b767e8d606d78a968d447b299a75ba1e54415d593998856d87ae0cb507a820fe4265a5cf14e2a5f84cc994e8378beb906fc00e2865412f1523ba4802082f6775479c689246b494280630cfff862a3b29fb93e2385f0ff960b74efd2865bd4223476a879dc0b1a07b095e80a3730f7926f5f51abf54d24232a0d5c0525741225ad339fc4800729fadd2768751b869ed09a840a45fa33cbde1827a9bec4c95b0b7ae6e0ed06e41ef1a36c4c081ac2ac224c38509fb8637aa33362e39553f4df8cbdd970685b0ccee0f0971879ae710ed2f58b5fd7b7ad32f29268b8dd17336a6091d32dacb9617c1792d65c6bef8ca941fdc8284b26917e22d69f9491bbc4a6e22294503c12b10c344dd13933b84863069c81d937e866500e5789ac4643f583ff40a4b8c7fe7322a9e4232da6c7153e9ea2aa7277b242a1e34b92cb3c02c8fab3d874624805d246f4cb10096e0acc9438f5a80c1dabd76462ca57b989d703e34d4fc880635381d2bc9afc0201f081aa78fa62bf301057acbddd1656409ae6daf0ab13bdb1d6740c79e773d62dbd816af3cc785c1c1c4a2276543bbc5c6ce5541a4dc7fb8ff703352e81171b911b090da8ade9de1de8b8ab4258b1f1a0453b89c76d0b18caba354348720d0de13e50e37c8e32017d5c6835dc6ba1478c073de828d28f3e4892d92ce4c409a8048160dfa339409f8ce2aefa743f5f95b1bcd4b81e03045dcdb6004ed350d7e6d80bcbbb545b8800c7a0771e366c0b604f1c8a610e1bdb6e375465aa2cb127500cdd47f49f60d9fd86c262f7b335ee4bc1355cd73be1446bca5711bdbf1948d9c3cb855a74a34038d584e753f57278b124e8bd853131d377fb193ca0d60a732fc7e5c62394b8c37b9e5f71419d265f194b445f54b5f7416df493b4e2426834c5b3338c3abc44dfefb8594d49d6a86f303166a00f7970c5fbc95bc937c0cfb30c27826a7cf3611b6fe3b412c2f8b777f034eb1f3a7e403c9d5a5aade47609418d34f984d824edf4fbd1ca050448ad78818db1df2ba3d2669970cc4067781c2601f757d7656856e0732710412c5ac36ad4b990c3d07805058732377eecb1f822c49d31d4d93acf130ed0c629f4ca088741bd0fef4f19a10b5efcbffba774ba37f5ae4c1c08e65c797c54d6453ac39c739eb1e9080d49049ea72125e5cd732804c54686c0ba44e1ffc03983e79d16fac5501995b4bfe51b9375a187eef40aa37ac45b2e4f4a706f36366a18641494216fd94179b37f4fb2cde3dcefa47619731dcba9eb3d409e7817606784ce77cb362e73afaa66907aafbd148dbc75f2edaa5f34b98befad757a482e98e71e579494f73a7e3a9e6e237bc98d2990a127ce7c0e41385640874d620d4524baf559cd79cc8c129350f4a9d77f73f61b7c671b3f716a3bcdac3b39247dbc4b5a088f57070f6e4509fcca511f2464654b42b39216c5f43857be2d9c9c4286ac6c4c837148bf23140ce3d42af800634f7cf8dc4d61cc7cc370049cc594e88b9a4765acf89b22e7c50419720b80f3cb104bac68a2aba569e40d11f6e3ac6452c9de174fff3e418a4918437dd09c6bf59f8b40529d0db2c9798b296b72dd4d4e18ada2b2062364fd3ecd313df7b7210625e1f5008fd10418aa5057c24b604d01e724611bbabb71555b7c28849d004b2c6c15cfb39fe7ff09d5544f8579071ce026ff578014b02e56815ba5e226ea6d5f46a8b81ca6ff2b0b626a688d6d394b3b84d28a446c2195411be7c39d2af48043c775468d3cfba70b41c54788b4b57e174c2562265cbf65bb4d0248d5b05fd5d21069700d203af73305b2de6cdfb2a807a87bacf56f4a0d7ba6c4b99ddf7b7736572c91499c42749bea557d03f0a4ec1a46d62354f8ef591a36cf5de6bfa986a4cbf57765f785dc313e61012a02be6f0ab823b0bed65c3aa39fb308bada3ee3d46df19af182f3caf3f5cced6f68eecfb55777df9b6805a8217f4fedf515d91c46c0cd668cb266a136079ad5728201d2c893c7732ef367884894efadd6abf894c76439fe6f0baf81fbe313ba5a80630b81e19700c8f007035449ae1f7c12a160ee670c25c196cb3e5d1764bae698dcbab9e413a2421b36e6521d06ecedeaa16b9e6318f6ec9f5e57cb87679b76c66a59aa4427115e46d119bdc8ad6da8b8124dc6779320d367fa99e2afc7065abbfe536e121d8d05e8ad948e33e60616a3c1a179b3569e4efdc244c613c0134de5c4d8202d964d292c9e37524a1a0756b12e4f9b67341c1e5f2294ef9fa71a1dce7d110006ee97aaf03e9db8bb81ad762f3c71e8d2bd443ad9c00187107efcee3550db45f519bd82f8cce48a4025f3e04b6b46cda196f82b1ecbc8ae5d0b432f84d92dd60a6b39ee0420f3f2abc24ff5f1e52b0986e73032284ad281971bb953ea9e1c3f148ce2c5b18446894d5f08e43704e3f961e4e38d7757f1da3e04ce3d1fe2f14fd37853bb254ee47d583f5674132312e3d97f9d440218188d4249a868a5fe04e7ee9092311a67df992e9e0c930979618e5c2e83a3f9cb971ce0af921f443706eac7c427d81fc62f0589c5c14005b1f4429d02a2127c99ba062ef558db1c0c8e5746f7236a81f003d57dd93a5b3984e2e9087e400c087bfe55e7ee2fcacfb29ae972a15289bc9b87a215f8d6a31b1011615cab70eda90cb688e90041664436b23abc1f604838afa5cf22d5302d782ba5f70b786b953074df945ad33cce320b2fe1a0ef9c9b48f9c5d4ceb83a7f5b3bdf946bcf7da47a9b9fa4353a56cbb5ef31199e217f03aa7f283c153f875d22da6596368d3478ebda0bf0d3f2fee272f7834e813908b7e2e0bf11ee8bd9a464ddd4434897c81e47c0fc34144994b70025b0e6290d6f3ea6564128b6472faf794519f58e1495b3f2b56089533ce9328b99160e9611c07efdd5d94991002882300dae2b9223c422e81a52c9d1450c43e11471bcb01394277356abb9616f065ec685c1091248a73c20520a18feebd01f1a534f7d1087fd7add0519c4c26bb5de9a1b7c4e8165711abb88f424df2dc1ffc41823c4b1fdb979c6df220c08a1a63de1bb87b10d582cd44091459d72087f821332d18d858a31647ad0c5e77d1f366c3c6b8f594e16a120893ebb0d5fd3d65c8510709e599e01233d298925b1191c9680eca82242452ec991d7e5efd02a61d4e9aab92ad29f286d911b9a0377e02adadfcfd4ba2447320277c0b2961809a228e4b6b3be47c5b4db74a9d7c87914a38c774c425c826670e6fc1951d857055c617dea5e10b48294b0788f62a9f0a57fa303847ae3723c6239118e8d4e81c3b549b5f462c21ea6d752b09fa7e0343fd7f3332583e950bad044a0188d242d37c5fda1c11cbb7e486977a8f0e07f6b3d1555b02c9a2c957bab4b2620a4309c66df9068fc79fd91fe411c419b6e73f5001145f3cc608deb43d56f9af0dbaa1ab1b863657865e6004eaf1f4f4d6ae4215e6eef4e6e137895bd54e02dd1884547ed100bb18dd589157704db6d0f2385c666f912d875e9fc538f5b393907e3323f26763987cc9c387bb29b509229b6734f6ce75ab76aabefb4de5d2183268cafc942d0c2e1a8f486aabdd72b251458d424da5eaed5de7ee27dcf41c2511bec1cb2b6e13054d003042b67d49f5af452cbb34842c58f85ba77fc75f8c67285abd8df9ef58334ac6ff7ab6a026cb0fa5dd133af347f263737ba9ff7304298098f0ba7b6a19644c5b047e4bc0eef405aaa7d7a363cff4d40763d3be1a7aee789d88cc4d0195f268b4b045eb6786c325816cd62b2f89dca8479b176f1708d0923abb136d96b8db3e61c12b7e53eb2374c377b6f523946736acddd075f15fcc755c85ad4904a4633ddea39b9a944db284670700f1db52844b9273694923a2208a25e002455c1e6568c1f0344b14558624a1bfe05155ad8d537fa70f9c6b40c8fe61dc2c57ceb32d435ead987d91a78940020b0b692470f532c970fee89826636deb5646cfeba766541e957bdb713e00098ab79078bcffcb46d47965838ca04109e0302a5dea7541611243d76a801b7e0bcd9840f34d0daf5d4e39cb6bc88782268c85f4b48bea59bd825b58bf5b9d51f6a9d3b8f55906a02e1eb3226e4f68074c8c640ce2a67d27626b3a546c864053794455b08b0072adfa456fcb30bc786d22dccdd6eb9063cc8a503f621ece8ea8debaa7ac18f673c0b40f031f8405365123b43e34dfddc4198de339d8942a358ce4023f8dd7cc2c286e4247a599bef5d7e4c7159b1ae398adb768a81765a9ed77e9a5e8d019f3513c0817e83a666f035a4ca47a28efad7301bce6e7d22d5761fcd9facc61ea1609ac3f4be55b34ea1a4734e74b4ea543ec3c55ff484af20fa838cde4d1c84e3f1caf966524ca5ab5c1b4a4f7ed6c7499aa199be2bc4fe7eb49e8383edbd295fdb093c964dc95b55c9bbce68498f739d3c59a596c4e12c4b1da3fbbc3ce694ab54cc4b5d565d3cf34e31554e9f62faec5cdbd20c4e4c624fab96de2be65f62a3402721098ae2c37af7e13b73ac9a91df0023529976554466563ab07f5ef5f81f5c9262425b6067d6ef6ab82c0dc4745fcf21d2836bbac4b42f4b21e84b2462280bddab93de8d86989b2efc3c09971c5fc8192dca70acaf3ccb15edd83837868604d80c2a2fbdd19397616898a6bb6a55bed38995fe72827b7c76aaebd2aa0d5fd374c2ba66c9ecd73e714590acb764afcff29a03df049b345b2873f3a55cafc614f7489073bb7462f063aed1ea05e8788392f3f1f7597e2d88dd449279b8dfe016be098d056afe4a47ddcc5b4fb268bc41cbda4be41f7d067dc5b707a4a8a1a30fb1940be6f3ee2235f5235194b5e6204061e66aa8b6766cd7f2b28f838e77f2e21007a1b9448d859c11efb8f8ccfca78a665e617f0248e1e32aeaa3869e06cfa9a0bd54ff4287e1e3e82ca4d327a5b89a225a9229518b6c460abb4b06919fbb4d09bb6402178128e54faef1757f23d019a952a5cc2181d9d3692c51321473c5d58e43ea08590681d9a9739d8e249914efd073f9c0c9021ec66073f0ec0c1ce10eece7fcb6471f50657ac59ed65faaed2bef063c694754d8719bf1b42561470bd00d5e5eb8b723b0425ffeefd53c9911363c46d76ccbc37162ae98151a3e5bb889dc5c433f53d1d9fbdc081e47ee443ac2a570d651ec4dc4143ea512a4790550c9a8d14f54612a88bb1da265c766d938270f6b08a2b34561b40ad9f90bfe77e70a5be62f30638bd33357c819fe0fba5e609ca44909b94f804fe6931bd9cdb39cc77bff1f8c5cf4d7042db6f4339290f488254df651fa01fea43edd2198a2d6f624dc56bc51521deeac83528970f1c0bb5dddd16317e79ace22103fd550153eb28c4ae3bef4ee7a00ef74f9543b763427d51838e05d599a561145a1736994bb05da7ff103a9919981c794b5b55c607dec362316cf0072b07aa372e2bcdb85b83a2483e5e902cd30718903a1ddbd873d3ce47f8c8fcbbb47e8a3d37448d5741f92d03ff792d3b0d8ed7327c36210d48d486b7dd40fe234c64638d710fd373236f12398fc3cb97b816f64855d2da1ea710d7f47345c5db0258c59b020664a2650ca5816e823e2d60a1e6a6f3d2d949689f31f315f748b3485a24c1b435536fa0a37b08cdf33568714c02554218395aa99c34d0307ec34211dfc3cdd35a7be1889c8f9668d9653059c28f957acaa3aef721264e9419a009d7d1fa1dd62c881e4821acae0a58d0a2077a48ff238f8d2b019fb49ad27ee88cfa23de9feced8e50c460bf5b1b75933dc2b71c9088afb9e28ce506233ba31c428e6af8f84bb619b1d6ce8eafceb990d9bec74b0265a98bbf495162536645a526a2dc2b0a92378f4e13b23d2472807abbf2e5660709f6aaefeb09f0d10872e6d4690d41b737209b93629ec09c33476c15efd14766b0432a72b4129e498b30b812cfd349bbfaa50f22546ed7513f9a5f1cb5a9c8ac90312f699d3dcc037d149891bc21a2d552da31714f71ee979ffac6ce85672039d086f69ef0241cee154f2a061c7e238e9d2cf13c1c4014d222ece43dafb0f36fe053d629767b6d92af45d4d2a53de5467075cf0ffcfd44ad5d2f37da8912232463485bb177f20c8bd9d77fb121fd5db95443a292e733aa76d034e1fa6041dcf6b40951fbfc55550ff7324faf193e0fa4d950f2b6a356f9d2a9e2256deedd5457b1532f2fc2a7a722d5944a947ffa7f135df706117d065bfd05179948381500920a6b9b252d93c24dbd37721c8dce4e184db4b0a1c92740d13c3bd9aae3a7d3ec0bfe4a9db0585add1ced3fd60cc31a2fec057cdf041a83428cef40773b21092141d8f7711f701a87b7088ded276e069e5e977aede89f5e761aebcb2ed84fb959ad653b3a7c1965a9269194c924b44e0ff1dd186d6b004e7e9eb1a9be18405319deae3af3f9538ffc5de5c957040173b23038305ba84c12741c41c26a50f03850e60b1abd38da2174b723f6ed5617f017641106cc6fba8704a7d85f306b86311a8969ee10646c9ad7b74657c8298e474e95d52f573f8eb5848ce44c01ec6c1e278e168470ce924bcf5eb06ff228ce4575b730009d8a8bcc9a6f2e6154d527601000b7a589be60997a2df0d041142eb5e8a3717b9afc5bfef7bb23812f399c24becfa4fd4196ae6aad9bc36362c23566ca3183ef7d62527c4a5cb2fb993e9ea11a18e19a06a556d12988b9b2ea73acf6df7aa38f6257511125842846f772b864a734f6a3c34f8f1b0e0fd9789670ce58b7e32822e27a84b090a34830d87eb462b833a53f68995d75cb9cb69efebe92cacec42beb268c70cc3bae7c3aa6163f76f65cd40bbb86a47dd565308b5329b13b872dc21766b18cbe2214cab2a7784fb7b8370210cbf520a17bda3a6219f59f48fb71e6d9906e9fea2fb3182e88901ebf36c33a69e0ef062db5da8818243190e16c217addf625a31e2edffc47e437e5b9b9e11c4af605b29791f985faadabe350ba7a016d10db58e74d0cecf3209a11d8ae4d1018b0b6f81f872bcabc617d3f2cc0ba0a7b339bedd8209d62a7e3165539c84f71ab45500cf1d6ba509825a9a6807394793f1bd2f932d6e2cb9aaeb9c2884bf6f98878c05dcc35144348436772c10e102f5b551615a6e829d4258c01135a0b8b95dc424387d2163b7bac849d1847614c6863feb0159a63adba3f9d6be6d36aff4361bb733e116f37917a41eaf9d9eb4d00f8346a4a62204c154fe88b0f98307f1f97cf3f5096155636e6ead9fff1b92ff1a9cd5172e102d9de0756c1d7caf520faa983f95c4a7726dea65ccf67e9ff9fc6cd85c3cc65925de531441e5433d0e845409174a34c5667d1465fe756e4affab75cd059a62b9f18d67f68450f000240bea4d77625170f8a4f617e82af687b40dfae91eb86f339f9eb75ff5e9e9b7b2165e2f68620889c13ed3e7fc2817c317a7495ed50766ea9148948ea1924d7a2e6b2bfe06d6aac1b5b0d80c011779cf1dd4d8b6d48ccb8c7c08bc90347c489106c8213dfa98649d3daa31d8f22648360d2591af0d1f1bf4ada6d8b3e0b9859e00875d8d4ad7e81384631045d5f85c6e08fe516414317a17290d6c37e4d028323b28dec0f9521b207f88c9c8ed79b9ba63b1b254ffbd3842f53f852b4e641ace2318a0ca1c1457982d08dc7adbece0a8ab41d003e62582e2a7f7dc97b9b346178dfeec68edd396e988bf374e515a0bdeb6c722262002c83c7b9602ba2586df3a48ded20b1b1b47673a271a67e0d1db849526fb2bfff718402e6bb33b7d02a78deab91f111a1bb87ceb3415373ee6b010c2f61ce9ced4c2bc010516b5feb5ff090f8a04465e0ec5ed2567d448dd1048a89539a1d7e775abc608c2b80727e20f1ea0dc7642f09a77ef16b6bfd60b5ff2b705fbe34732e6eeacbf16ce8d7793928159af825bb353012149d9c3ee82e7badb41bf5447014e74f6b5284c01f74152bb86a4f4d462fcaf4a61e062a24c77a12da0dc4093f2b07097c26b038d91d8afb45718d1b9292a244dd0f5f085c393b3ff414fa79a86801d73da6ca3a6c918d65a1628f8df63cc8298cc51cee906443449ba73839998391f6b2ac66de5827445929dcc95acb0f08688493108372bc9d86b243dc21eec9e88cddecd3ae299af18265150aa4285ae462f2a27ef17317e1fd6ad18e0374dc540adddfbf3e829965923e914f68186079aeac0a5a53487c8c220cb236e259d5310b76c52e985e514f1bd1fbac2203109bb667388aefe5b0be6c88c9a09a3851a127d2c359dd84e11ed500f12b140a6449cae7ab95e33dcebcad9f4406e43ea8155e4263247d2a4ab7123eda08a2b16dfc12ce55986ac6fa5ae98c0e0843260f941a6343c177198e6447339ade0c95f92622f1ea04f29c50c177000035350ac219bdfc59c28db6fc4b9f26093a079042a34741f2bc0b5b0ced773f6e23d93b983b3315c92715b259f26d8c053f0d6d8f3f266541f266b738309fcb0a24488489da0c932c5b0cb82e83b177d1b6a1c0b45ced71bab5469ddeae4380be9adb1f6e3c4e46472c9b6ed163216746411096389b669f89a2ce45b63e180411b89c42e35c59bbeb8c51909ebfe29da022c6910f47f19a4c2df839bdca4a42eda3225c4f20779aedccd0d34b84dcfb141eee82256615a4dc95110c46317a8216441be83c29aec6dcf8a3df32973af9a47e3d1b8cb10c81d0851af9bf870c45d022c18a36f6e0e950c4121da50d0c6a0df66eca054083716fc2e5eecf1e2ee628165b65534547dcdb0f79cb3e46807e52c220fadbe1c874ac24e4b5d20452d6dc40dce7626a943966b297f32e3f3cda145959beb3855e0a6d82c796674b351b5edb5f1263dae750595db7ee1513ffce5e836c1ce4d2a66c56dd3a40e4dc2b5c093448ecab905776cdbc87d5a3a389a4ca7854e5d93306978b5b3c6c0932c70c75371f9ce4dbdc558a7d792f3a071d0fd156c094cc8670c8c155d50cbbda30d9befef9f17135e07625126a2c279a5174cdb25bbecf925d98333ab75d50f2980cdb8f51e10e754c4c6a0ecf5493a494b0c3eaa3f327ed4a4aba76ba445eeaffb72bbebd2585a965c3ea513f2ffc28aaf4edca72bd7660b0a9ae96e2ee59f49b230d8f029844c3a16e74365ef39eed5e2bc19178f0f26efa1ca50a9083a011c1f376d642ccd7bafc8c7c9a4e04497fb0741b143002e2f92c5b1d23f7a991c5a4173e809bfe10ce85c00fb84190ccd19b47592a769a6e09257d2b7abb6b48e24efd53eed00b0eef76bc39f00df0b77c2b242873c5d5f1bc58aed1a9bd9aa7548f98fb7a84074a43a96235c9ca39c82ba37699d9f3d977da83904d237da0cd7ce45f67ae7a78e526c516abbb51a0d2a1fe0e64674a818adfba845fb1319ab354e29e2f4a7fe76f75c18cd2c14c456d519c060c5cea7a8c1dad6a97988f8d6b859a725c4528fa4f2df0115aeac4c3c846a4225d673d5cec4bd1b48d0cecd1b515d94c0bc0ca66b3b952e555e13a6782411cd3050e2acc8c26458baaa963de9f9ff454809b16d8b1214f7b60db96ede1840e3bf2997f6b04df362addcf0d15baa0541ee9c44c06890161bd03cdbd6cb8fa6232b45a24e1d137d5db4b5fd65cbed64649d6a7acdc5261a35beebcb484c00d4af7c5c86af0684bede2bf3d9a138d5df3e294a9b1a97a43bc582216d37b2ab8ca9c3bd113ce2f569864942ff2daf05b0615b049015892b1048456476dbf2bd1b2b6299b7fbe371ddd93d35d4029c961bdd12dbfb650b0c7c616acfade68c1219d713efdcdc9b678560d7793cce1665a5534b27ba203fae89bf2eee1e0a62b52f7ffbb831b5744fb27d96396ec36aeee670f9e4c9682e68082061d83bc90c27f350ff03b3ebf487dd9ac48429f542a3d5e29ca3cb4ed2d9f6dc044b96f8677433e08bd220a931a68b93672d0086bbd5aede2fe51f43fa6802bab3f78115247436d53d326a11424aec00fec783c63689fe29ae751ec8f894d6c15068cfd41bac99e61b17ed31bbe34d3bc0f939888cf65cb15b2d716537049abace8a496bf933cc4c724e99091185901312cf8f421cb2db4b33bb34c4845630ac27944214bec93e6aa5db2ee72d833c2a7f6e7f7c3d63347d9afbf738619daaf575b38995eae800ee12a792091544441a855fb33b6dc9d43d8718f54d41d0f3ac882e7b2bb6a5e83472f51df49292076223473c90b669e6dcfa492b56a56524f77592313f185924d4f9cfce8fc644eb5b2b819e5982c8a0da1509bba9700543424e0ee39f014c97ef1220a594cafa214c10b9aeab00feb595f26ab956c338a137ac3957cc8a666fe3013db24cabff9a641af2c69c8df2a401fba27cdc64914cfcb429b2159fccd10cd3edbc58f52667656d6c02922e932e2d38887b14f2d4900a20a7017a89494637a590cfd7edd386a38c6f02c0531a0d0f0d98aa0fda56f6c9d2448d9be3483352767276d92f92d95f63bef7682aea202a39bc2eaf9ace357cb25ea45f785f5b30bd67ca6f923105386929e1e844cbb44e618ad8e6f55352469a71da36a2c20e784beaeaf0ce9b1898628c9c4b413bafa44624e147edec8bc5e75bb392371e45c337ec0ce24c7a33ce8c9ea7f018bfebcfd94b366a74e094b5a8d87e5e6d504d628032af880f8bade95fda657037fc40ffed3a2fa62856032a65d79436d0dce502d92ae5b9815930ca72ac8b45565a8b385d8345fff71e49b20ad8e439502bfaf33d6cb267e3672572383a7c95b7d196de59a9ff7f9e10f91733b0ad3f5152346f9cfe481c0d8f3807869dc8ede28ec269e62a04d490d86b4ca5b1cef9f3d6833c48e9aab1ca98cd5f995fb288c9a630a5b4a32889ce583881f42c1600eff0e4c12ee15cce3717ac764580441bd75732993264522d3bf334d2f99b9544790b7b316d79c27350338491d771c650c10595487394c644cf60ed1ec954eb55bfd3ec563b97700da5f7d2eb609408f70c63a4fc3084e5dac755f6eebd00eceb8d77f9ffe3612f6010ad7bc02d12d8051d7eb8f8d37a5df6c3b70e1e1912068aa3062c6195313088eb687e9edb8c4ebcdf0cbea4693eb5b0e0874552bd533e1fc892396ec17377d8ebc9b3f75496a5fee5518ed73a9105085cbb12addc0b1b0bc59e70dfc4e806ebf6075842aaba73fd72c2e4cf943b7cc5f6f18c3685aae02d698722546ed81d121eedcea9680a87d53553b32a8318e18cdde9505a2131c4c4b1d29b4988c2e1763c5aa6a71ee78f9aa8dc022ac88c4039d84477b24cc2912eea6376f800f6addfe739a6155de12d1e3214ca469cc17dcbb43e773f8680bab5a2f380c705c03dbe09362d7a6914960dda6e672c90474d23d2383f198e66c9f1cdbbf6d727621c1b5bb72dd4ad6ac61b41ec142e811bb4baf0741caca1d343a8fa552270be9f6c8c87163e7c2a4f652c9ab25f4e95a4d0bec5d27674a5fb034ecba98de69bf7c7ff5b1788dc21dae21a2e174e8a4f7bf321ca542da91af73bb701b4dda30d42bcc10841518a9499b7d3ad53a6a9656e7eed9f37a084d48bc7eb07a1835c7e45ae1ba5d6d580c46c1ca52f1d6ccb9d23b73c383c320ca2c7340a743b8900f75a19126f17c59c0d97b2dca5a6d692f57fcf5e27ca347b0669c91a5c09f8161b6f84a0185335e8082b2d91eab8248c3251f2d2a63e41f0850c1b3ea2394ac3355fa5edc39e03b7a0b4e60375002b41d9a7c8ef3d7d8f04b7d4af129a3e0c09b1d76d869baa0c760fd04a8c1f1b7a20ca20cb637e54976e2585451b1aca64b6cd7a1eb70357c2f15ffa7c41c4ccde366c86ac3f0a2f8650b4285133275d8cfe57c81f3150ddf11ef88e0bf5fb2be7a663d4ac03b249f0cfdde4965de261e6a6f1f9a870a5f2fcfc2c7fee217179b66d9a8b22e523be13df4675eb033b6acbf754c25947c70e7e29caf694fb3520a4b3c203fcb844d8e5d4aece4efe509deeb7737a1de4f3a6349cdad8f1410862d45769b962c24c3704d253d381dc47d336722f213891af9997e2e01e39dc07f1ae6b2fb0cab3fa3e24aa1796baa8e5923614782d6a68aa2710692e571e4108059cbb1fb5334fcc55af7db234f6d6d56ec7f73baec033795347dba1645f8d80fe5c76316db7c26aa3abdcdbf8dde05c1e5c0a22abaaf7855633f4225172ae4cebc099a5ba2fdea6d5bd31a8634b32b207ffcb38f1471164611c92c78fdca7d0fafc2bae2fe475cfd24c0d18b3e579444e227d47d4b867e1ff7b1578716c734775f9e6bb78efa0c2d2d9df06f73c3122a8f9c6906fe6a00e99cd515e762042567c66c66830f70b5aced4d2c8a9a390a5b9866bb9b7dd02bc01a0435014c4a01ee6316ca8e05116dbeb0d1b36e940d5428c4343f1af396ea57f2810af23f2f505f304bd2dccd01ec2c03c974e5b00b8e6d28ec93bed81788d31441673cab051f210a8a6f0c0228f2ed50c9eb66024b8ec630a7dd5d1f7c5a43121d9845562a05aa6601f14a98bb2705121cee7af3bb3b3445a14fbdfffd8c7bf514e24fec82436ad617be961ec3f05511a2f642d0ecf9fe0a976f28d1e6c61b8a948ea53ce21894fe701efd3eda1b4bfb6a4a507eee20eaefb7e588e68d33d6484edd18478c853e2a0f262d4f165fae435072d0eb9fdab301e2becbb6e857f098d0d9939896ebc453a319923017c8b87cf84433e0f20c5d6a94fe4cd5c310fe69dde9170031e7dee9bfbb1c33ad71dc5ab30b8189fcdd8c4857a0b2212c5ef35bef1da5251670c2ce65031654cc2e015b45b3d0bbd715fbde26e8499ed53c79156d933ae1c98bdae183e074d229aee71412e86b2215a93d2d0e270aca0a8818833a5d95291b837a5c50581f53cf7085d007564a00ed5d8bb57e3c1e9e36ce59b7ef31120cbdbadb57f71dd67a94e08fc206ab5d8bcf699744d733ac3a53b5d305f04e892cc47cd2d32f81547f580612f042db0a6a16bb4d2e9bedaee29c06371b73fe79264f57e73def53c475f0e6f962868ebf30c90d1c1cbb8825d86ae45465c622615c0436fc567ce57fb5122ed17ae342f38ccc612e43733a5ec0ecb137c9c32ea926df1ba3bcfe11fb540508f5c6471ce50a2fd3f5a86b49c0a6d713a62f27afc1a6726e5538240b0ec6b9f513998d562329ba5cf64a9c91658d69a41ea5a4d52705c467d3c7a125c123b79eefb50b296b5f3e383c7fab32850918f7358b4de5429e428970d10313b2a7fd3910bfcc10386bbf7fc8a388322d0cfb3a2047302a01f7cda3b1a117b84a0a00ab70ae6a086a6ebb6b34448114d97b5b37bd870016297ef99776ae94f64db4e5a820b13ab64cdb7787442821a85efd83be461f33193baaec4d7c398f6c05ff9d41244906d250ef12112768b76399d1295741e5e603d386bac93ff8815db5e1eac36dbc07dffa4c4609eb9001aa560eac729e6cf869f5e3aba621c93d8c8b329f02c95701eac4a11e1730b3ece88d73cd01d6d6ae0e15aea245389a75f8a0401d4991daa7440fb8df83df1aa02e9098d5771dc45cbd40c11c58757f9ad8bf4f2378c3be30437ffd1bcc00f7d61b2347137906622002b18d4b54efc2cc59da23b6f0c9fe4f6435fbd6f7e32cc99e336b3ab2498b6b7e4cad07de0c2dff8d36da1b1d10dcf62efd1b65b23e4eeedbdc657251dce616c80f36310ce818bbaf822c81264910ab6c0c5fa6388c3c1b03396dae8dee95bdf04ca15a505ae73268ed5cf88bce19094d0d30147aaed5951329a42771b3d57fd10306e89b57dcc003528de47114f08786b9579b308b39a2a5b55582bf6d9a37d278d66f0be67bfd7a45e62953f5acee5fd488594fd4f9bc7b063c103688a802ea232febd85ebb44c88b43b07499a3e5194a8e3af287098ee1a596bbb812ef5480e6f507b10e8172e3979fb4168f77b883d268b4decfdc8ce69b64055145cd333b365e7a9bdce5cc65f9185bdb444d52b26502f497337549f0772f8c7ea462def63951c0ae3ebd5127225858327caa6e9a248b8731b600222f1686960d3167b7c7be3c456e30b809f63ca2d096ae1ba822f1afdf5f2924ba1485c6eba01abb9fd10c7076225f8dc78eb3b7f20fd868c5b4ad5894851a9fcaf0315d859e0febfdd916ca31ff9f31f1d871cba6816a4102f7ef697165ebe95738a487b4980a7648c464de840eecf7d4958752c4e50e0ca19ad1c8ae8ed3679189c6a482430c000398e4ce0e014fb57b510e71915c0e7002f208798690a74b046d464c8b962af91f3af3bfc3b7c85abfd5d009a709ab25c61ce567948261f5e504b3503f35005d2d4f183b9f6130298f43a58a818fb6c5ef4e7aa17bebdddfa35c8b6e0b1848c4f103142993547dbe6c8dd127ee7b55700b48b15acd918341b51c087492703123bd318d41498aa249a7b8d877ce73747735436016a5d126be5d3cdd62633f8606fcd7b7ed744f47ee39b12d5a9090cc85c9884169c65c653658208811c388289a4a17280734d8a7b9d360d4685b34e667e34a789ec9632b42a421e40aa1e9b8c0b39e5463e6f7978b439150465c6ae4a7630015de873c7b8020484c642f2d80c16f684662a91a82cd144cf41a404bfc0e67f941504a0f9d691664d3e1d8bc8e1026bae36797fc5ccc45bd46bbb692f90cf5e5b7760e68950bf82d8de467ccf469abea1e6e441152717422d2f18bfcfd1d5b545b8c55295fff1a573c94933f2ff483204f86fae616a63173c22947abd0ce3028c74044158492ef786fbeeefc193166ed02eddd425aac93b5a1b015c16f0f7a736431f4026962f6ea0e3fcbe9473ae36f941565543933ea2d2801539ba546992622ba8dd148140432d4904f8125badc4336085f46e9b8eb1e139ee4f95b735844ec04484491af8acf43d848a9ffb12b1667332f881e3dc4ce16674f1ffe2bc6a513eb192c252b162f2028cc55e0dcd80331d5c1ee08d7c84956aaabaf12bf78db0bdf5bf66453f1aa636df3d3e4ba4a40fa2c6ae7790067700bdb5583d95d5fe26b8247442dce067f6a93bc2578c02cc53d8aff0f6563fd4379349fe3ab28da69a30fb9ba25542e7d62ea4754a0561174efb0edc78a3402e16e081d34ca6a600050fcf121f62d1374cdba02a081682a8aad59411f3f09ed0fb50e3acb5e851bbfa29f62b890a5d8b5156b8856cee5d51ad27a4367a9ee1f6aecf10f6954fa4b6066ba2150338a6dc57450c770305fdb4c938689577ea162e296a2dbc15a68f9d663ab0d40ca98fada8ac560e44b023ff6b7a3cfb28964e4b9382594d4335b0b71079a26b934710f30182fb2fb9eb85e3728c631a905f4cb659394d74fd7997f45dfa5b7ba25489376aeaa1c9feebd5d73c32728ba6dc3ad47ee30e774490351b38cbe82b3d75f830f7d123b8ead4d9df96092b88efad536e532914058ee8e5068ccece831dbbc1d5b1ac7dbececd0137f421f5b97d5813c4a138fa4f818b6ad2f95ff5b0ee055b39c4bd1ee84a43b3ec0776edff3b1503837d0488f1b16359387eeccafa82c3ea9c355925bbd2a315a121e53a15d12ebb2e4a209bb4a22517903ac87c16b71c3d93b34f6abade225fc55281e858c6bc96d5afe0c68971f9f2148b9b7f90fe72893a44160b601fc6b760efc0520d26084ebbc190890ab0adc141bf7bf2fa0b3ad053d4e0f04a3deba208c0dde0823bedf93d46c486355f822dcf98c0cd6e4bc7f06dfca7cbc47bef69c7adaef0eda3ae829d2499ba0f58eb1feabe8c7286f9d1d4f7412c81b05f7325434dd27e5fe7b3aa9672c42d5ea4c4a823f9d1d66d39afbe272cbd2391cb30deff11bf1b08a01895e60edb59c155badb874b893d03c917dd9687d74d2fa6fa58000e467cf49f64b7d63d4e2188bb61004457abd61aebaea1acc0e2c4df27b14d0adeff60db94c82f59b41555b7fde28b043821c5f3e179beafb27ed996ac595fcb4019037c316d5609c12acf813eef11b8d032438153f948fb5074b2637e439e99dee1070d4da88139eca81e6682308337a33bcab2bc581acef85a22dce8dc4a4baf36c4c80066898a41cfaa9045dcd4d0286c3b92711b3242bfab1dacc55a8e6ad4bcc7b05a3cf193f4f963e714323829c1709935212c32dc912899e768522020bd255b754ff417caa1411894a504acf4ab7d8fb357c97cbf461312b29db397c51ba77a61bdee9dc17a77c1567658cbbd07cf24e5915642de04daec640b75e635bb400cd4a4b477af2c8c83aeb8766152091395467a04cf6fa800055f57340b7204d429cad854fa518e7d7144e887e897b2e1f6321695fd9970f16e7771b650acbde66315234e6312fd3b46ae8997b1f9e0252ff3adbe06c126e371b93170ed4065522d3a6998342baf5a8b8ebea2cffcd6d3f56c772580e142c8dd704a1177bfedc4d42e32024d1c59ed576c796d4b1740907b81884879b7ba30f71621a53d9fa5882ad70edc7f544b187322edd2cb5fbad4c028c3db7191287f0926cc93a5ec12e28dbf44e11e109d86659f6b6b1f470b9676c53a4078c24c720da2c7ad73bbd090dbbcf6cb3a4e4d7df5d69dd0588f3e012217db6f6639f0f26b97c33d5e2462988ebf112124a99b7bd49a44634541ba2c0f9ddb7d291431acdaca71fa3246b5b5f8953e6b5173431e55f1d20040a7bc5f8bfd411f1e834b64e180af18581568fa179f64fb61b975d6a12bf3d9dffc9da27187ce39d03982d7617d888188e1a1f770e73e0f6ac4ae70cb065d5a1c5f79aa5a1105d12e31b491bc6dcd1b6803f80ab62d8e0b46e9e6670c5dc01ddaeed2b2e3ecad387d1e716fddb6464488e33de59a8d3e77235d3435371f6a2d06716cd56e3d10d76053f6250303fd4da77dd9bd1bb6520570aa2941a09d6c81bc2f73a3e55f0c9ae9ee3e3db2e6a55c2d7b93128e0edd349a40df1a82e86af21cf9181e7c81d31b10799c29570174d65d04e7bee4b5559620c083295dee9a16423a9aa5b37d4eeccbc3e75a6493cd5e5e163576b20d9bbbe532b29abbb00390c6762021817e88842a7a444377f65430107d3805a31f1d3ee1a7e824c20a5183a1d69d480b2f93ac8dcc475eb358b172393bb2259125514c261a5d7fe31bdac83fce8f936a0121de8c1a3816fecfb87fb7b052d7ff5eabbbafa0aba6c43da978c630cc3a7cdec6371e1d84bdb51e1cb47eead46cc8dc183178bc6dd9becaa1625c1b3ee795eed91d1be797fa519578f7f6c185aa9dcf8e6e2e2f8105d53166deb5511b97e832a641131cd441bb754b362dc6ea013bdfc79b2d9cf7c7bf03ce298212b9fd523002ff5b80f6751fc3f2f8d3af4b667c0074e08c2401f8c1dad1a4d03f6071f31aa80d5d818095cf30dad4f38400098de62e65ec5a6285b7b7837a5997520562174817ff41d9a20fc3c8c4096e15cdbf0a615ec5b7f2edae3898de8d7b498ef79c32cb43232ea571bc500d76221d380342a6ef3f4a678459dc6c0753f303092a0cc782a0dbcb46c100f09b0f2e3d38071f2a70b522c2d8ef37d4537d464f001b8bed43df74859b41c0ac146db0d5048a559987611be8aecca3d01ad55f5a3458aefbf3354ff328d1b8fd4d5e76d78f0c35d454cc7e2cd22e6376b537eb1dca8a22bee5ad11cb321a8ce7387dcae3a89f68d15f160d54f0f5bd5c8103f9843596bdb1346e195704c9921937037622a274aaae6279c5e99392965dda7130731c810a73bae05c3fe31ae1dacb79ace3dcc7550376815baeb4222e3af1b2a2680a584e4830af07e207cf8b317df9fb7717b001b89a4a410d82daaa24f18cc81e314b55cf07028f392601e1084ceff54b34d7f8abd05d9c259bed473311ae70016a66e733de6e911e4a076cdd360a328ab47297e00bd8b11fd41cab34f781326fc6ff9c52c876a3a2660bcc237dd230cb9d2c3007594b19ebb367edd31b3867ae9cf7cd1ad274b5083f15dcc928c4dae1f7d0c8dec62f57e69f94d13dee098b5ae1921bdbfd90794d58965f6c7e12b1ebd4ea694061224f6ae6a89d5eefeddf682a9658c89daa21933e38a8b84c9235f121c5d518a9a107e9e5d25bc762a1fb0d26fdb7980ef307521aba6b91a46e8665cd8a60aea6ea266b6e5f44ca730d358ad90d1e9426c4c37accb29a0ea7abf37c15f5222e7d59bf0ebcccedf150f11f7eb76c2100a118a824a1a218354ca3abe9152a6653ec1801deebee49ce03c138d9fe6833a6440940e6a1caf55659a20f12551cc5655439a238ec6bd163f7bdb6def1b66cf0482add27138d8d6f9c09c87fa7e6df4b25d3a12f11e58742c61d843e5785c6848eaf879cf3711227684f2866b8c25d89a8e3b198a2d32b3535ab935add20254eb52edd8f1be320a19500af76be56972a1b165b7f76b93d4ace48710d91d75101ebedb8f06e20eddff7a7d93db4e1d0f4926e098a4f3da7934275a7284f69d3c23bda11016bbfb3c1c2e056f3c9097faa69733f26e3b748c4ceeb23598f65ae8365c8ab7ed768a06541f1703c3eedf16d36f583a5929f29c32dc0b9a087aa7b587d70cc949a2757e42a70c95b0af36d26bc0a808f92e5cfe28e33b9db56f81c6f4d80b90a461d21f6354b9177f589921b7651b4044c5201611f38bcc05f8ad67bc36575639fbf754ddf3d851d86b016a41db4a71a7d9c2ff876248c13c421a067bbc842aa2fb521f5bcc94ce940a0737c9fd8651b24368b7168862d9987539ff62553ca4109d99c6d59676e0aa0e180a9ef96c9d8523df286bb88bea1046de9b862a7987f18f5a42384638b3e9caada20ade38ff446dce9b1242229cfaf90d0b22a5753e44f9e016dda77f9d14745bf0ebbf96b8f9dcac77dd83bf33fcaa661ec1eb483198bc50a9857e5b47b17669988174159148dfebeb7e4507d25e764488751e026547cb3415d3f9e70b6f5ab69a4102fd0c0ee5cf50d264bfa8d1835e78ab42d02ee45d54e6d7a185b22f09fc2222cf58d26291bba3b3172f7df3f8b54ec2e5bdfd481be3c313eac6d4cf612afdc1c0be522ad3bea0b19bcff0296767cfc6fb98da97c40320883b6d80ef1dca3494e95b0678cce124d66b21a26b30a79c7aff3d644f93e928de8d7080d69db9281377e0f9cd727c58a49dfe8fd5d0af03549afc3ab5a9c85e2825d7974eead7fb6386bb4f4b6edcf72ad7b669cc12b51bde99f5912d8b1493853da5da0a4dc2b2ee251e41cfb540f486c3199b6f068b8466cbeafd3f2c0f709ddb63933042c55647586053386c4e84d44e3bdb620224925edf32e4ac95a4e39f6c6c8deb484cedd782a99ea28bfd0b3fc7ecb5121dc84e02e3cc5159974613a1bc7febb9f3e84ebe6b5262c2a99cdeaae3d85324166ec58a97f349bc4c8e45c4e4e17bd0e6ccd75946ca3a01c07dc991066722c42b927f50864066a1c3dc1c38a9b733752a5c205fb78be4e248b8100b2312f85f9d519365ce15fd6e332abc9d236c1667b7ac179af763da00b08c5074a8723dbd3ef5b484847e7ce90d4691518b3c770c6c510b5c8f52fad03e38a7b7f833d042c002e11323ac2eedcadca07ed635b6dde38cd3f5898ef2117723954da596e113cf9eb5bfb6682e58780a702a0d329591599054343e588939b8563f7416f5f36b7b622b5f66159aa9297d61b50138e0fc089e381ad1f81df75ea712ecbc660012e18fee5434b197fcfaddc777b62dba2fde5e79ab7650a472295448c9bcac9d16504956b8948b6113135e433403849830330fb378a07f113ab0b05359597f186207690f693a976e53bcecf4cd5310e91aaf17f65ca37bd37809515601e6eb59200b337ef9b893cc2fbe0f737514c969fee5be5a42f47de0ec56516afb39983be33c5e92174aade24e42289204a9cd69f382244db12b84b4f3cea49d638d6e4f07d3e7d5629670f2ecf4b7f2fcb7f83a0b67a35416bd9fa104a138579b0d194b9fd4422ab510f473cf62252ef8985138a17342d29d78eaa99c7c5fae888fa4bc23cd07a9b4b0e7cba6ae44e685f5e4de087c09345f7ba6fd62a5dec3b519130753b1a20fe91c47a02fa9d4800c97cc6e62e5feb8be32b62c0431b0645a7a28c7117c2a5eac7c01f4089dcbbf1d2e7b08508668ed09377d34de0db680bb801716653b0d64d02995b630117eac5ac29452b3233866bd6187eef817ea887786d0c0e7611f7901d998e16fac8019cccaa1585d76354cf8ecfae93393f9a747978832cdafd6583ffb91801ddf2be404f694e40b4cefa6c52b015283feae8864af7425304eb45329fdb10163b36e14bf0d5235a704a349d3e38dfe618e815d47af997b6efacd53f2ff0466adc61dcbd0ee0bebdf9d9b2c50e65554a26792828b12c9d49976019ecc0182bc3024c6e97f57b4e45c4976d30417dc9d5ab71a80e874c0ee87a796ffde361aec2f155a8b401a1e41485ac2c644aebbcf6c42e23f7dadcae2c6b40fa2f3c8e2331ec34d9ba93166d886d458dc18bf1e1a274216be9807ba1faf5b65735badcaf91df7606b98ddae6c16bff8920c7b83c2e10456270d53eb28e8fdb07b2cbaf713820becf475088be0405d913ff2e59bf2caf415ecc4db66c8f24a65fa84fe1d4437482bdae17f9f40e6b165fef514efee8893c75a5b7e286187ff3110b1d18d4b179b072201a05e6ad6e98b2e3ccff145326bb2199cac962d9c4c8b926f2772de64ee6d83caf48b4492bd37c49a3ea7ec548156ab1b963a707c8ac9122ca3f0eab2c42a1ea4713a6275d665c29441eb179dc600d7d925d29f75402090bf87eb4fffdef81bb8a6aa0bef1e6e36170427fcfcbeb85e37b1ced548135d96c31ac2d1f406f47b8fbe5808373ba9a843f992f365c66c099542d9d097d3c6a05b2283d115505f763b1f539d36190ef7d2f0da041b00b4aa4e9e770f6a6287eafcaf3ea9122b02334b72f36696ee25d00b2352a452752818a29a8045ecd255f96ba4607884ce12ec993133b36c93e5b916d9726c930ee155fd9ce0a9b0557f29398035669514bdba322598da90e0456261aa691ca9e55d66af49ced848a6fc5ba3ad6d2fe3e276ffb65a28e44e919bb007f9e90b0245005f629c72719b678fab6a8537b0aad0fe6204a22b420256ea17dcd9004c5c8c0539ccb48fa1ac1dcee89fef63169a398a59628be0b088ecaaa477f85fa2be985fa367b00788d351ee0a1348bc26609a4b20c28de436751a5531142b348cdee55682e9b47942780ccc0294dbc0dfba70490e30a4ae3ae5a57a63e6eb2b971f141a806167c910543414e6d14495840cbaa219d2434d71685657a56fd05db5f46c13c5c0103043d4f598cf2ebe411f22a51ba9c4d898c9d479c539f44951c5af07ae68528800f38016647abc3b5afe17268978e4ebc8d6a26956e7757b8e63d877b21a5d05eb39180883e5f1ea28b4a8bca6dccca05a74d79289beb8eee923b1ac5abf020ae78aed5f58a04d68949e02d56bf1354970ba6383239683afd59d85a4abb614df232238cbed0b2faa21e7632fbfd0fe8253c85df3e24ef5c05489014445fb80e355aa5c72cc1d713712d75b5b421701a0fec58c45a6b15fcfee916ef4e5e4646d3c2560e0e8f0d162aff5584d938aca786473158c442347bf5c64c9f420380b637c6584e6d720551947e7ebcd30823853408143267b4891d91dd3fd4cc935fe1b308e008fc259fbc8f23e8e78eb24e578ca23f904cb573b9a1d2ad9288b76645e252251a76e912c041562faf5aea7892b517a54ce45f3257043e7e5f6c405b22c1d228568509a1528279f84cdd0e71416c86703461f083330122e20c18af2f7dfb2772d8d9b3fad3303591e68dedbad0af0c1f68a198f9ea9da3eb970624cdd708956026e1bee1b16ab80a1118d54be50535587181cb37fa331a5624250733d0a962b7bde36890b8b7dfd7db4774546d101096a7cc07044c54f8c4445475d045b4870c3872f0ce621dfe84c5cbf75f41b1fb9f8a9bcc2c921c48e3147ce3c38d565d4a6f7fdf349947e78b1010fd41929a08b05fb81543969dc46298340094e8ecbb3e7e23c7320d47bcb5f9430bd03355cf4ea513f568b725f91f41910ea8e7024232781a36324d41a63f735457433ba0e90f5087aad7954c0b65df3524899d8861ed1e826f3577899bf394ffcfb2fd5ffa8763bdc1c04ba50c084925bcda440320418dfcb920995e4545a2ed28058a7801405503c2cc85763c565732a168a54e4475e44b9def0238220d2db4d06a1383d6e329e5d2f8e3cb12cc1d97ea8c7edaa42a7aa38371304f87990875321b3a826b996f16bd36e95561e64e6aa5a82806ff06bd4806ea504971ab4c05711362930f9afed00f1528730f87124118243314338f0c6fc7049f8eff721906e4af528151699db468ef575c35781cd01137a21ab147c52af70ddc279fdef5ff13e3217f65669495c1ca84fd66c79696e07718abfb8ccf4708ec6a12aab7caedd00e87b1bcb6136887a9596605414f121b7a40188886a6e7f785583394d288f48a7957c2702fb87293b0e30c453416e4e8045cd4425b45f2b06e54d234b572b6a21c381b961f79445b81e6b7f978e65590fa9ed832b397ffa1a8c9363fb609502bb74a77ae65e12070c6a31206beb417d9cc995aa184a4ee0f8a694518adc49b21168b2015ea3ec13d7c77f10d0ebc659a79dd48fb94b777fb2b360c2d81ff3cb9a4d3e6e02a128911d7a0ff53bf5934d543fa93551aad6fa9a10c2611ae1f11244d9308c6cf976031484cf8cbbe9d8c1112dfcf08804e0ee94a190c4bbcd8ef9440d5453404c82d0d947273dd0c7ad84d003220069225d20f8b5c067735114befea0762d91c76de96728e3196372c867a02823ece7719aede435d64c2b9e3b5293dd746d595f2fcd76a167831c1e204c4876aa03eff5274cc2fd4b385d6fe7da76ecdc5d89cecf8c78b94cdc7a80d66c03352966d9752614757cb1cd740058d65004becb0b12da61d5843eff218f1a8c3b5f37a2e54cec91095b150d5c8b4e381b1913128e9c19bba59431c92a24f76ad72b914eb8c60dc9f2bb5631c3ec71a74008a97608f053b3702ca58a6bce011b6798c799370d9c5411ec0bbc9794ca2c70ec9c360804367a48d4b7eea2d7612cedb770179c882e6a471e2256470c18b4dd62edc4afb8c45fb8a98083220192867a89dc11e4f74960ed62cb494763f737e47bd2819fde243a86befac9556d5035e96ea3c27461f4e6f053e0b271137914aafad0180a3f98512ac73fa24734327135ac53d295216e9670cd83900a735d75ac3aabf63d7203d9d0c8e3752f3f25effe29c1bcfff7c7f00f15d3bbf83723ad5c33162313c60d6726e05d00bf5fa66ac9285d310b30a6b508cdbf27ec49001f2e9072b19a5ee05087c391004e10270bd53f15a78254afbb1a48ba97ccf68478e489b13d66a17916c299d78880f91dfe2c2c115d2ab5a2a41cf585e69ed6a0cb26c2178964884a61a12c3a0f75cdd712b27b7d198139b289eea438fd583c505f456553cf7574fe262e74840d5674c9f7c300e65ae14e17b5a14be059f23bc5e06f607dbdfbe0c4e80f86a328fe61e2369c7dbebb92e7bed7d70d3327ecb1d8836d8f6932a18f558d2924954d93449a0e3be04e3a71357d0dac3c2276e5e975f34bc6f29c7c84fc9289d1a35839da5f03cf5358592f8d0cd07bfce87e3198914474064d8922177825b2bacef006a1307335e9ba5eaec223e6dc34fb2d6944fe7c403a2aea5490198cba0598987d6aa289a8a080e45d3138b47523f977d3118171e5b48fd04d651c5097948dbcf6baed680bd8354e92c46a8d5a400eda72377d9b74df063aba830a6ddc9d1187830c7b137a8aed1eaa1314fab941a646fbb0412f4667b48fedb3e32e78e467a01821422b3fb36080d7e20873b679bf348b31df68ce30eb3a22f6c7e903d5bb2822a1fc6b78c75812ad0b44e3645eb26f4e37363648aa1ed9040b175782ec31f9bd7f18da2838ba18535614cb5966702f56df6688a4cf215e210bbc8f9506ac42bf47537de990dcd3c9f62fc502df9a2ae54565ab3d26d79a433ae3045e494ce4ed15c5c7fbfa3fa6b61ef474a2ba512f8eb41aa5a422ebe2c7fd83ca77bfa01ed96b8f6690749e99e317492226121020a2691a0f5690219a98de3bd3fb3bc1a57d9dfa57876cf3c615eef524ab19285e5ced1e5b58baa42794c15446b681b899ba74831d462907e8ae36ca70b7e0f551fd038f48c57cf6e01917195c781dde4f78a4b545bb0ba5816158167190476534cb1a07fcecc88d8d00143380621019ae467f6746909b06d27ec011529889dda722a34c07338cfb68fb684ff4b545eec3ae85dfe94061104498aefd805ed33afa3f3fc7b7a08483126b98ac112f1b7cb2d21f1f495532f0bf09b4bce0afd5b788b88aef3e234f2f8c4d0953327cf372c0dd218248f0346357c4397da326e7411015ff83883de55014d1d34d81721e1ce1d0fbc4396a90bd925ffb184ce15ce9a70ccf9e0e1ee2136af2a32baec95534fc5254a47849d21b5e6d385924a30d7c31e0e145b52720f5f0ae6a9eea4b5030357123d58dfdd752ef8349d13649279117ac45ce383b30d407b3aaf9fa1a37b8a5927b4f62fa88dbb179d0b01505a7ef6a36d8ca3291101e10e5de826f383ac13dc3de12a5e43aa80c9e17905a15169f6a84dfbd074e3edcff59c69b3b477bf884b92a7f9ab3b3b506698dd3688a22541d5c33cff71981ac118fae444db85d1b5c2988ae2b3e86c94b706a4d39e9e8130f8f606603a63ed929188e66562f8c52e0f78ff598ed9d12dd342a72387373e48eb1d4e61bd9618ddf3454d1667c66091f7b70fa6cbea956a51155647975923c90398bbb630a629e55e408fa44795b67bb2b71200a48a89ba313303402f6b6c7123bca1d32535a4b090c02f1b567dfe48cbea9f25fdc2b29dfb881357aefcb0a01c49040262ec71123b8e631739cde5cbfa15032c243c24c027a78cbc4399f45961659fe271a9bb18602ec13d77dabb819f27d7f198baf1253bfbb637caa55740dcc27da0e73283b2437fb915a66f03d9043ff7e6d866fced520d5acfc2c001016be9a50939a9ab341d5f191aec14556be3e7148291682a859b7a2377516ac0185b73a1a911b3b85e5d1e6fff91f9d14db491aef13645aa53caf70cd0bee653361ee9dff2c05d37bf9335c502f4e3cd33d7f2d8b74f9acdc47bc5331685431427af76ba7f07983dc9ab87341657f0a4c3e77f4fa61f9e2fb58c56ba81f4bfd3847dcb57b17c40431524862c99d3975a23bdcfd9b4347159fde92f4837303c772fa455a18cc508884f4b0db299d4de6c71757a0a2e0a13ebcb330d4b3f8b2532cacf1255cabe3102091de94556510ef9a5be5249bcf9715b3d57c9aacbb886f3316a78566f896dac843f5ca05ee4acedd3bf0ba8b21ad5526474ba6a6ad6d465443965ffab6827e6738e88290019989abc863cd34dc3b8a6b388d42a41d8c315a9476bf5add6584fc535acfe44036cfac06ef4e9f3c766b360a53574b6e32b7d8ed880c4bb2c8c80d1646880a039bfde922d1a51557c68d143f202e3a359cf4d5acc4e2cf4d62b0108aea842e2287e6f99779599d6a0f667ede0854032d472bee4f4897cb4bcd9391c7547c0f43cf31bdd4fa2b4a382db63a2ec1a1a1cbd834fd05c52604090d2e39b0ef4fc6e88d2801efed2dbc9c26b12bb02cba904609bb225f0966cecab92ca02b49b7309e66f8a5502515f7f09f1cadda2fbb166d2582ac434812f1d18f6f0f115cdfc6e2fe36433e28f84a55e63a54f4f86b2592d54898a998ad5919378e53c6647310e5f4b8fb876e50833b33c10cd6eba12f10246f6de4693558c7bdea514f0059bc3695a33c23ffaaeaafe8640b6364aedf454338c6da7a4a04cd426a2936f8e8c68bb39aa1dc4d29ef4e8a51b26c4e4c9628a6e7eabac644feb4cfd2983ae4787ab7395bf087666509cbe7308bb6cf40486ffa64272482bb3802ffd59de7863e3fe051af714bb31c8bc15be09ddc671772809d0e0a69258b14336c3596eaa13b6dfaa8a2dde14030875c4a58e369ac51214e0d22e8555d8c56316cb29b97ef929ef91fd12e8036a6996b8c1bb5fdfb869e9a260924ab6d14c7650c6c3b1494881914c5bee697778e9cf7dbddb72161c78f873186ed6100fe3cbb9439a72d0c4843601e64b826a6a68f43bf44519dc4e5eed75e8697028b104099e5c9eda4c5f4b0fe23216bfb44774f6d5dc5d6411a0a138090d53a947e069523df5095166de20c92fc3472b9aa7d7b39163e358691bb8699f6824f372042f3351e455367a7f88b3b530b13c4a2cf2db85fde7d51724591ca0268dd8372b1bdc90748382ddab1d64a04c6861cbe52a0286acb80101e5924a7e46171858b54a8e3cbe93b31d6b8cc9c303c2f7079eefa2ae0d03534d01899aa0760a7d9b8d877a95be1d35ee8202ce19d20a019db59b88041956fc9bf69d02a30e63fa60aeff85d4dfc25f2986acb1135ddeb1ccdc7c23b667e303411a97f1e5a9312ab6a9b16117ba6559dca3bd69d3470c201c503e1a2144b577169bf7fe3d8583ba372cfe968f7d1e119b0eaea980aa99f4deaa8455fe446a8bfd075d83c5377cb1e67e0450053989d0bb36b2a4f0b9e5d816eeb4d88792bcb6ac21b8977412da2380de23017d612a516d7a61aa9407ed349ce805530379f54105dad26dd65f8298279b43f7056dfceca6bef5f470f3499049e21965bdaa4c45a4b9a9a160ef57eed6bd56848126e3389824cd7e59d6d360bd30b41595524e9a82710329d8123ca861bacec712483ba3321f569b9a14a53fad513db1da97be3c63626b9561c9595e64fc0713d99023e8f1da8d6e4eece792baa9b4d39afefa4dd50471ad98312198bb8ee796355ad96643d7adfc55f4a41fe28d7077f352a308ddc1481bc43ea5fd069919f2d3cbb8a10ba609a28ee8c1c6987d4493deb168d2f2e0bf894a03ffe17ba19843317fb693ba8242557690b0de8e97c1046d902dadbf84e55d5728c12a4b671a2f4410b104665ee3f06aeb6455ff7de340ba80f945df6d1d5d0ec9d08668b32ba0bc7b5ce4b48234a957935abab682b8a82948593e74757919dd53a89db77413498abb2c0459fde551a1319eb7709b392694c3bedfa09584147885fb78d4076e7065677f34401c5dcd3eda9e1b74a37ec98da9f6b6c480c18f373e32c9f976c28ac7c590fd5c2bebab4d0c7245513f7c9d49b93a9d752fc1ae767b1aa890e52bdad6af3caec6655d2482a683b8fd6afa921ec807245cbc7a2b5878dd186ea1a5c90508050a4222a07494fbfac566968c0a0705a1997b81f8cd0ba158b649babf2874c7b1d73be68d20debe71f4fa0c794c28dadd17dc825194b0ae657c81d7ff3a46dbe03c10e58390e1835a609eb8fa518b56ad0abf60d260975820afac61d6cccbb25279f09e41f989c395b4885e88f3c41f88abd74433f7c985e62ce498709e685909e1b2d6aa8beb8189adbaffcc5fc1b12de3a0646862b0a1dd447d15f910451ad6b73ac39c747a5863ffe1c6dd65120107b0e224c41c21e0e37305a55a5adf414081498c1c750d33b6d239f6f061593025538add523c95abf0bd3699d7679e200cc250f2585e673e3f7b0f4761681d1927b5859f97fe3e82392dc735e500e865252ca98807d4b78dd5599560c142c2868bf00aeeb00ae347a4d6845c9d71bc322512c17596cf52e17d885f25669db694f76114210be1ec30bfa3cb58d3e9c8d64a2978fe69f431b081c6c3f60a848acf4c95ce8f802d12750bce2efbd125c6dc9cca9dc20985c82a9c7599abe81f95a7edaec8e856faff5e995ad0357bf4c8c5ec1f24bfbe28957ed3c4dd50b869b6e99696f335a564df6d1a1baaf48802c285a51682870407c11697ef31b418eb06bb083e1a69fce3e5174147f0e44a9f6524f69166d41ed9e8ab4414e576b75c55c2667281ad7d6da8ce628f7fd49a3563ce28611c58a3925c407d6d04e61fd9e1aa96bd7c9edc3f896eca7393193931704c608e145acc7688db72a52f21fe0eae6b9bbbf0f27518f38913dd5f654e5e000415ae720660cbb5b181350906bae94e721958eaea81e9bc8153b8bcd891eb53fc4a32c633b56b58b854c02b0a13b82439ea911297f377fe9c635efd8b77b5365ef72b5adbdae5da31cad3c5a9a9d0b8d709dd22e9814e70dd253638fb5aded158fc32f94ee52677dca778be24fa84a0c51456e3da5550d41f749172164c645ba2a2a3821b3b3fdeee2492ef73390207b0191661de3365ceff2ec099ccd15c70ff887fd0df25860b91936500c7a8554783aac1bd13ee2f26552db0fb216b49345a6de7719976b423e227576349b670f7d88b4c4fb83df31accb406595cc35a33f09d03c894a7bb28efe8eb004300d59e95d466a7868baaa8da64a51d34655458ccf8f27ca0fc00a57d8d4004ac11470cb9cb3376bff0ec99e5bd0cc65e0b2fe1888eb465298a10356c15366347c41324364838568c557bbbe43e2b1a1064e490dfa896b27af99b171b5d93cd6bd9330b21a1a0df0cbfcf6944adcd945d51d611f256e87b9be15830d7485c1b0474ed46255104be35df657858f1aa7ae92e7bb98543039e3aaba8f2d7bab2dec486ce132ec2c302b28e21bd5d9de9b7197109c79af6c0c9db6231ef988f9c9fecc1534fa65739d8c789136be3013c17cea49d42a8c9383691883da362ac23aa20558c4e95a936f04e2c884734c398d02d83763b6774c0a98403ff5a84b2c603705577e28b0b2558acd74bc0c6832f892d3e831d69811af024bb062a1226cf813b9432d2434b513219c8be399395eccaef8eb265b03d9857afcab0650fa398a0fa95efec9ed7d4490e146eaf5c5e350ec8de6dd2e02798dd86379dec01b96481f1575072854666376c0cab72c023c7f5ddb844e932ffc54d8cfa8f13fe147948e3f85dbb048b15fd17ac26544ca7d6374a3deb172479754b2d05ec6a661d3c64ba1591a851348ac0146b157c7f17a168eec9b9b62eeb4fea7828bc5935a8aa1b26dc7d87fe04702ae5befc5a452c7eeecf4bca3a8f1a5df4b0a9f2fca6148d1406064394dc0b5b2937987969c87bde2695e2bdec39126a281b3ee7b85c0717671a52a11973b1ab52edad4f92d58583beb070c996a075eddf65080709db9441189956688177b5c4bb8f3f4c8ff693be79e402182d11a297ade87eea5e8f57c207eccf52c857ff1adde40a70e67f72efbfdc96fc692e7fbb56197906651ea21221a410211e786da857a85707d56f5bee6e394bd2e08659aba2c7392e770fd8b1b65c4307280901b1e44c576f19fbcaba326919b5286544fe572cfc92fc3350cdf3afe3c918865d03c1522c36d5d88403d9db295c06e1e754119e4d5b8b9970343d3124b38d79f072b3deef4a6f3df0bd34d2f15ec90798ac05c6acc998eb7fc494589f58c63cb3afbc4ee239f62b3f7f9b30bdc2c84f4ff9abe88a91fb0ad70889d5f38cb387c08166c62b47ad12df21dc0f161828b953e9580100b9376ea10b93d3e88d02087ffdd142290dc80f6abbb2d5846de9c50b0c12993b2ea142e10fe0d51e2cffa03dc5c942c6f7febf1ab8a5e5b8be7e6cb5e80a47442294f40ebfd03b4ca27caabc0cfb34dca0217f2071d1a78ae44f94646eceb3d052ad60c1ffddef5c625e88f9ebb0f186d38d1ddb9a3dadcf87d3d7f242f80c710fe353c4a11c5583f2dad04da6264f1b1c0dffe8766c7728c14a74755b9b88acacd8743a4314ee8c82c11555b89f194ab3cbb0d797251bcdd288ab9c8fbd3da24fd96263f9d377116543e012efaa84a5c38d702710f1e49142eae0d192d9d41e1bdb4cf5a78efb182e29099c3102303911ecf200066423c84068816ba08e3ffb2645084e867a782930d8f1c8e15faf51af40744ae6a543fe86d0105bce1fc4cd3e8349ff4104072a548b41c366b978aac7d642dc570a3a6fa208b83039a3f1ca1955144d065ac7756757111555219c99177880e2e5d211b080ea977a153a663a21002a163e1a9c09e05cfdae839c3d9f3f94be4157daf5811e8b39cc9eae11144434ad60da11a645a4e2786eeff6011125d14da22f9c18a92c5467af17a558f729cb36aaf5dc71ad1eec33a19d2157f8bac9d7b6e68f87cdb8e2b03c4da99741a0ce649b7ea33a7f527d31bb9d5c209d3360a2b9564c309fcce3ce8174fc7e8b7431257b3c86cc180ffb46b0136f3d8505d0ce563120a42cc615cc749a32b946fe3f47db3e2c44d4d814dfa7745bebb22ef04dca265089799822ea1138a2dfd678e88f5393be9eca4fed146b6c946ea1fd252514552fc077b6653f15e3cf24411d204e802f4000afe452752b10e0bb71c80aa9555f1c3d493179a0797d02f1f98923ce237e4c994ce31337b0c2fc26257c43ccdcbc1cc3d86da1ce3f1177c53de4fcaecd146d10c5af092e2d63dc637b385b86eb98bfbaf6599c578edbb9856734092a6f188178c01e2b0294209b4d0e260b318d4cc3ae5ee76de29039f7c9b6445c9f62058635841b0b238c856a4baac9f0eab4bd36a86753884198175adc45ca1e5876cb6d0ae7893a0a1b536f6667211b5169a1084179a270138f847fe90fe231fbcc34e3f3a5ce86a3f28c24673bb880c34b99dd0c6cf4133c44c89d4d460dc2b6de380bf4a0153607fe3241fd4dc59767911f4eaebd52224c1a2ba598278bbe17d4764d3dd0efacf606ced406338f0c52db83aabb6eddd48862af91d9750fc878509a06945a118c622badd41b681109e8e494f1085aca875d1ecc25f9610b97919ca72782ba4e1519596313f98126ff92d633ff71f788592db17084bc3cc6e32aeea75cbc25ce63eb782d501d73b93da085f898a045d7d57424c6d7c5fe0774b33adf93adb4b2c32334d7f42ac9fa2df187d9fbe95f5564f4a7b5490162806034e4aad01c2052b6ab0bd872984d2d47423631af6e030d625f114036e6ee7ea491f61e0a42ec7df5cb9ad0f3f7c09872b72807b86b6184ac2a073576bba7c89403104ca28708066a9cede96b7814ee2a3c40dbfa47342d20af90e7c546a5dd0a230cf7a3b2f586ce7b950ced38c529bc148c72ec5218e5b80e708455ab89cfd18ff5b159ae756bf42999463aff7dff99ad93abd3987cdcf773a4d3d41e511733289d197c2029f38ff8eafd4ea37080abec25e150aa3b03447c2ca7b31ee9288de6dc3928e79a72a4cb5b33cf02c5c2bbb15bf35a7e0db4ae9e9d27d73816e9eac403deea4ea1b36d09649db23c303a0248f2110cce756e8252915765c5c06bf0e4902fd5e7b6b32c1ee00eabc97389353e914c7cd8d8adfbb52e5e8cf1f1c77b39bf33c6156ea833211c78ae00331445d515c52d1080dfb61cc71e941d65e86266891bdf004ed2a284fb23e43427ca3b907fcc464ceaaa3d9966f7c9efa20ed222f46b7b7a825347e2e590651bf6f262bf39ed09495a2d2ff394dd45c1a15a3ad97d81dadba1a6eee8ab95951c165ade3fcc77cd5ac640344e0d4656851e2d651256b8db66899d192b388607229375e7fd81da489079c770298901f3ab8f3da61c0acabce735b375629ccfb8c33fb855e8a41b8b937220e24105e49edafbb150fad10f1414708c9217864a7a9cfc678904554e44f36360dca0ffc48cd467e9ef99f0644b955990e0bd36e22072693d569684f2c5fcc67cc7316fea72f5a845797f255b67d51817b0222be6329d14a5ac20212d737bc10e9bc3c2a7f818f63d353bda79e477744961a75ab3c05917eef661f0e02cb95da92393b49fc6878176fd8cabd3d9dd16eab6b3a44370a9a29ec9d149369c87c3761dc29c7f205e8b83fe901715386f1b3fae884a522d494f0b293948da8e0e8e1f5aac37e66e59e490f1caecb04912f878f56f50371afa7f375baf45ac01fdb1daca7d4c45c7d71958433a035e756bbf1035077bc912bf852ee86872e4617ac843a2564bf609d0406f5f9f288d43ff123931e574d627c1dea71ff3f0e26cc7971d9dc58eaf40ae187b8c15b5125c945445622f62249fa56474de00adcea00eee6d28f098cd0bb6bed7f314ec250de3645072e55b65c2021ab8d80b882fb7a43a20030212c9533026facf2868abd9ab0c6e2e9ff27f884fc6485638118b15283db2cda675e665ed71e2d5c30183ff6c6e7f4e40f15e2511c978fefd2323f0678ab0324f92b7ca03b73f4f7fabfa7ae5073431ab70a895b38343b1ca04c87ed90dd0fa1795e1c4c81cc15dfda806da56ee344be9c0d6083f467a8494554e84a02e7e994cb7c475456a894167dab4e0febb177e0a5d467a5ad92dc492e983cdeac84b6ee7e0af2a38a2845fb813e31afd80544ce0f58a30afbaefb839f2a935732b5e78238f01b405077e6d23e69a594caf76ecc6c549d264609603415bbdaefa5150db96d70820ef224c239536d32b7b878f072d50beb6569b0fa141c99820e33b1f29d411725b4564a03e3c72905457e7970ae434c5d050cd1349380eb78e85e261caa56d142bc48a1efff863602831e5d1233d8e895a04aa085f7f6d6458af691607a01ff9ae8e873eb5e8fefdba922f83a165dfd7f1da21e58fcfb3ff7bd830f3b71ea8abf190b6d87d559fc0f81b95a1ce5f5abb31d05f48a9621e96049b6348968f8ad57e628dfbddcc90cc0bf9e99e7cde37649a7e526fa5039f8c59d546f4abafc6b2d62db9cf413e69724573bf5910c4b19f08b74c50addb07368d358abb78c0007fd7f638d740b6fdf76ea328c4037ef487cbe29b5f23a4ea73206e036b87968707375b1341bed22ab3ae3cf7d4458c6e388e7ed2dabc3dfc379ddcdae63e88cdf8f5764d0622346fe6c41c2d4c2a0dba52569220a78eafe530c0ee6b83176e68b825c3c692e051e036e2b83dc2f35c4c2479dfd57d5bf678b5529ce0bbb6206a923298d23dd0c87a1a2dc18b5b846bd8a7be7ffe4d447cf9d604ce7ea082f969905c0e9d61bb9b393a0819a0e065db91b1bf09442308d42e54fd2253d58b4cd4b5850db715cbc846fd24a0d7c9e6f7683c768a0f0854badfd92be1477c9fca94a1a6ec62a8eefa58a5ac45df235fa28600f21394c47cc8cc4bbb4f5f4dc53e186d01a58c7bc15d70896d65a6302b361acb5b4c011a5aa8a203df32291e8107f5bc1126a4be57e4017efbaa1f0c0cd40b1cda1215e1dd9e1ceaa79a4f82f7f9757522746e10ec04941f8284631d94fe821f717fc853d30e3258c381515321a5b4093e036e80e725069949d8cc72d5367de7af39d727824ccf8fcf8f8dc4f9c7ec15d96eec93c11164571e091b5d26103cbcf0f456211ebe76ac72bc0d9a96f7dec695da614eda8b31bec8563adc08eaa158a6e8f5868fd66bb933306dac4c2c6773d21bb2d0a302a1d509489376ad920430eca178471eb21b5ad243a9cf42cfec424baaf1747198e726f477c27a78c2e2b47d93a453a57accf58405c74935cc8b2d6205ddd01dc7e22c161e75a3c83b540ca8de66fac0e8541067398ad9e59bbcef011dd58a70b69d9723c34dfa9de4c39dcd0c67079a2cb1e107fb89693be4989cca78105c2ac06fe28a022a9fb1c365167a4e3d4fccf1f39aece76be3966a03d0c0c7f825a047451f76ebd7120d887fcd0753a82d85b874bbcd1f100dad93caf0b4eb350ff3f8bfe5e9a3f7a062e7ae71e9d004f1f749df3811cb7007a28fb4dafa47237bb27f0613778be1dd0f357285c9feb436c4a0328bce0cba0b603f30d3e579fbf92a4573fd4eafdc0e10fde0ac0fce99c6ecffe4dd1b7155c16d5a49c2de997b8351d61b29c38cfc932f3281020cddf7c9809edc53172307dcc85f4ef666908ef75c35d717f90c80de959ffe45fb559f9efd70a44a1d5106122309dab205ee6fef8d28eea13ee4026d4a53a924b98acad2adeec7effd8a1fdb243b39e233c93c7a7d904e7ccc1c69df5870cdf19522293f21075a76db52757f58e52ba37f15c651991e4326bcafe0d3e70b0461a0f0f6df24677bc16d35df97555066d91d293f3537f548ca624dabc333b0d01550a4889402cee9f79fc15bd4900e20d2db5e2cadec9816bb88bf10dc64612c81e0781eae7f52c7e03637bfdd02758e684ecddbf5cec2493065235bed0925bbff870c6ec7baf7c7e807eb044dcb0278b9e55cf5c57dceda0ba18125911a4aed7e52f2cd081c57f830f6060c98d29e50a76902e844b46f57739a0f91aaf0e3524e400003d9f7291cb9559491c387c792c0cf274fe0b3f70ea68d9f12ef0d8ad0be9b53e8acbe722ac1b9e3d41c81013ffea680fd292dc6f5726d51390b55362aba1302b21ba2f1b9425b73fefcdd78932eea2a61fefcbd0790d5d0f287331b1c2e4efeff8ed99f3dec8898d2c082000adc717d5fb3a01640b7d2c5a1cc2ea675b122047b501ff079185cb5eb3e03a99c3bcf2a1d1516883cc4854577badffcdfda8b6f0732e2f26dcbfdb6e7967fcb607ed3161cbd00ae294780e1d5d8dc275d3e2527cb4f1345fe4518cbdd714f2542b7307b0eb25daa02e763614e74f70eb402412a27a5f9fbbbed3b4a82e30233c5892fd897b1be2cbd66c4e26a65a651fe94d652e5bc4dd97f4214b846a92c5e97c00061eeed421f8c20e5861d7f4f4e38669e480c5a124ab27100a55ddd8f888394b5450dd0e485ddbb125003080bd533c94caad3f9af248fc5331c0df5970bb5e1874c0b904365d8279b1d625d3de1183e97d698c055e003791bfc6e99ca1587e0665d6e0a6209dbf9740822c494d17715fc305ee34d1aa5fb04a0eaf5e427c377e7f6cdf83d02c86b0135d06ff06c014b0926e0b42c3fdb77c0a5e9858df1fa102c3fe2c450d0d7580675e98becf1751821bb55b7d8cbb315025f1c50bd42fa8c6590019f5cb667fad651fec585c9d3f1253848673935edb37ee8e24c39b23f6951910f17c8d3d3c64e292caa8a6dfb89348ae1841b4be554f92f65acc6fd18dd8a22cc856662401bf62456a22f5f0e6694e7285ca412c80078ff6ba7f61d03d517873ee610909cdea72a7f84e576d72c3cb8314e900811aa9355288808f7af9150b51e54a8e18467edaac03df080e5ca9d5700b8c5af2009fb28f6d9174b4c4d1bed3442ffd1577e331aa0ef8b0481f7b4c5acc1f65aacbb92d7ab359ca52e3f552aa06a931db60a0be1d29e59215b37c04bcf799397b3d6494af2639ec4545245876f82d4cfd953aa7c0085a774958f3c14cc78c2008a2c112ba307743a4728fda3052a66e35fc97e489b124121d7eacc151d5c5ed465817cb935df03c030d78c34c8860eab2346e2e3eb84a258b779e27d0d3b3b67cc20f23c29904d8806d2a8b2c86b34be24336793c146d5bdd2ecf549a1e0c2368184590463d869d396f951bbfc1abd573de2b5c960afe37ed5411a2bd0d443d6e021b5a10fe947661bd97830b54091be05e37225e8d6df149d39ee9b674ad80a793f8eee1141076e6c630d339d4701ab9d70d0bf30ca0291eee0a4fccd5bc8f19fcd31561409681f20540075929133c5322afb29604bfbaf61f267bb6f93d76c21ad1e7448688c617493aedc9d7d31654ded7761ada136094e5b7771fc82f86b7488eafc1c7c790384d30bf7971873b3dfad7d77cf705af3d2ec52830a7acea64fc1ed273ac26a7105d301e492de7788301f6e7784c0fa69218c1cad3d0e9b9fa6e5a4d6bea851d778b35796092443f6e46d15df470d18139f3fa12976326d48fc395341767fc52f3e04d1e363dce486490ea2bb04720f1f52c0d52ba8d6b3ac085ed51f2563f7ed773ec8fc163fbcf06b8dfa6ff142a9a2e622022e62bdf3f1426441429ef5b8416567a43096c324457f2fdb2b2b5ef96e327ff7f43d20435518e7205927b0e496a86bcf08cba37722a276fda3939b0c4ae71365854663dbf785bdbc1e5786432956313321fa49af5a8445d144115eb4eb08e351fb6bf1ef05cfced2d4e717cd80dc0ebef0f9bbd7d6a0bb8808d4feecbae0ab78349490c280eb5e4b1ebd11797a426a9ea2a5f91b0db5149d3551832bb811f700b3fe00e66db47395e1af043705a61e825044ebcccf83f40a54d9d1df97d832ba28db9243ceb2c9f53cd6bff030a622f77e2ef8218415f515f9bd296351a125cf2cee29eb40f9b99b7e39b0ea54bccd64b73c3602bf7f90ea194ecbbb8214d0feeb707dc3eda4fefb477ea96565fbfb05d69f350c40e14e581228fe111251c38fdef722c87e78df7a92127c51f6c760318f7255f9e07d8bbbc049e7e6c3b770d95a02a8aebf6e433c45e10556395476ccc29ebbc4d5b0bb1ea5dea0e95bac9a13389548cfc15ad8e73d28ed7388723d83d2990d640c8acf02b092b75e41a2354ff3d2a55f5663c7c6b8acfd9c12f4e11be0c6645cdabe3d243562d9a57bece5807ce7f3ca35108753242a855c00edc1868e0c477c478b35897c4f37b53e62d03607b30313b44abd22fa13c781705ea26ddec6c426e7fb92377d73884243805560e2a578488769a42c6d4d7aef7e8c87c3518da30312d1ea27a2202cce6ede48692d6798b02c3a0253dfd5f0c5b7ebd7f45a1b2823864130ac44daeade9dc5a08b6d7ac1c42ead66d6eb40564b9a7c0105fa97ad85d34349d6b79d5a27772bb579cb45d6ad74012c62653ee2d9ae1c2b3bc40dc37b17d7bf068c52c9d2325a98d389302f5d0a53def900b10f39fcb9fc77a0eb5012430056ec370f8d8a8ee745a2a6fe6036b5355322dae78190167afeee5d82e9ebabef01b40fd0ec072239f6b12ff41e0a78630e37b595b450d85c9d185a01557d081b6e0ea067ea58d60cc61c041d16f682738a0be31760621c905cf9652c7b7942872f134e2bcc96e0b04cc176047d7c71199047efed307580e34f97a7ccfded3fd5ecb956c44b2d04ed4a4f5703090d2ce06f3e7ea3d7d86e2276e2956bc5d68c04f7bfa90fd76e7083f666bdcc72ccb0bf4f15fb51b176f7181f752e466e07f575b20cd7d1089a863567d55002c4a6668ccbea66c86f6c81e21344f36ba6704bc19fb60558196e3ed232d8b0c4693014ef58d3b11b4ae0c74f32aba04ac884901b3390d7f27f6bdbc300a5f2e37665143ee6508c606ec3fa0df5f8b7968e458f5deb08445cd2bc8e1101a0dae3c1e8618a3b72a85996cec456f7c59678ef2ca7798f96e59b1839d4712d1cbf5497eaead23a8f33e94a21fa0473a81a073042820b73ee2e6a38503b8ef6e456ec1f5df465a01e2b82643c4351b0bd3667dedc77c48fbbc6ce2dab2ddaa3d4918adf9d53cb938a500aef13f758eb8633e461d14ca827ec36ce97dbbd4d5804ec70ed81c4f4c1ab53a4ccd6500356abe47ab3b8ae7b59f32f3ee8fbc3154db49ff00c3f7eee3d46a3dce8627576352c3e705e443494848b3fd138191d8f3bda424e701d606a1af822947c80e828453715a84bad734ddfdd3f9bbd24c3d90f826561a397b2c37ecd5ab093774b2cd7efcf362cb56afc328036d68b6d3b0201d288857c3a9e800a052d79b2dd9728f8a0565d3e0210a8331c5101c02f3401050917e9a58eb4486ad181541e2462f53c819a98bd732dec8a89ede31e5ea02e2560e54bec635d0512031db7e1bc36f5f1804e77157587b74ca748b9bd48e5aca1e9a428952e65bf1ba3320b34e6acf57074d9d002a3cb2017a58d4d3cc3380c99d09d9875c55f7f8b35fee447adb58f7616fbda3444d4811ac8163bf07663b5ad384866ebab970e08fbbd04515ffd1474b28c60e47df53113125e5a46856662ce8794284d97ecb8a86dced404a2b1f5cd2e3c79a699029078f1f4270d56b6b2897a90710df71dea89364d8207048b253721dfd60153ac47656b3216592b39f2db5e818a56132919c2a6eae9a220e16327c05478e7b3d14dbc85952381e245237b2214edb716009dc6fe46f6e203cefe59e045a56a5598644b949f5d25e7c0f54eff22a99b1214622bd36a4ac68f8d359941cb81c6b6e86ed045a544314d998ba23df04ce3b2af1c961fd7efb524136bc94ed44d350c81299fb0ef92edb2029ed1a3bbf93ea01486b51e42dc0965b6c4171d77e3666bac055c494c832acff2afbc9645336e0c1db0b7ce3068ed4e7c54ffe105f9dc82c7d0c071bf7be1c163524385506c0dcbb7dde4dad1418c98dd4cf851a2c0d89f89f5954033639adaa61b5654acf69634b43f2937d0b1b984498262e374186663e3320bbb8299e9706d65b81811fc82e6b9c64b9ea80db82db9db7cd7acb6dd4cef675a00b378080df92b24346cb665b0973cef804bb483e8752e2a20d50811c19ca95b6e5597c8e988cae2492172bcc4d0311fa69f0e08d8fda358315f79977004d05969f5961faee8f0e118fd3d3997d0d555d8a75f09e6b81706d81340c3eed97e1effc72bb496deff97b17a2270814adfceb8f5cd23abdda739377ee43f46eb40758822fb0f261491548f6b5e36bd25fd761e240b3514385d28a487eda966f1b93e1db0c618abe8a83a187646ed0cb08f896c671d4db42b4e253ef02ee46311d1cf79d9a18365a3c958660f4ddd17807cfbc0c74a6656b500e77cf414744b399505e2bc31cc974d5611ca76f24a032baaf985aea31b93a22e3cd903dbb43c540e337dbecce3326d0b76811355a114139cb1d2fb25f5c0eea4fc57ada1a362aadb136126e8bf34d446742c8936bd8bbb007c34559886c06d1aabeebced8d95db25bab71bd2d11a8e3af79eea83a62e9107048e7520d0e829de69d2d2ab82a6ab33e2e0e54b584e74e1ffe9ed3561ee3fb07f998339d66c58c2c8a50334791bc6cad49efde439a8b76d027721f918fe5287d8cd4973b0f267a651312c0c29441f8a680771ecfffe6a5554490e1ccd9affbb06068def0e483a0578160fb45657fffc594a1638aa5a09d0cff0b7669120ee1201ebffe0570cb8468a0bbf2f151d7e023486f08231134a84be5f95fd9f1d4f5f71fa5b9d4503088bcb999e7c78c2816f0954ee6f25d23b66bd487a70ced2f44fa2a15c574b4c18539967db9bb702bf5553f75836024eb5f564402f4828ae8d6a997046b5e07d5c9186daa27e3fdf8935595a11ea3ead14101584d6c2bffcbd742111358fc626f5f70bc8a609cdd9c0f10702c4aac93d2419476a44c053410babe378309f7ef056bdf5bb910e4e77caa999316c2a651ca20c1e099a2788d68930c601e6dc865178919e9f8b0c32b52febc129d3faaadcec1a4d8850fb6713df4a3924e51f4d6bc1fe63f03ddcfd96a4a935ed54278c9a1937330ef7963654f102c41bb6e0ada51bdfce5f2d0ed81b46fd90cbfd5e47c0984d2accb0eda7b89d819c307e28753b5da800aa605896d7af58501ae11141470d162733cc3c4fb57f8767fccb198fc811bcb46453b78653892e0aa30b506b27c8541fbe12e23d4ab75f8d84b3ba451c2d3b476da55c3338c202d159db314365c1c6678fbfcfeac610c60a5e381ae5b1aed9d762fb75a0635a614b141bab7dc6f95ae9e765ba3f98a0bc028c2f5777bd56fa22ae484e73888cf7545b0c64c4ae260836c06da8d1b6d5570d017207f3fd662d9ac061566b666af815f252122fad82ca1ca28c0d50fbf41e67fcaa23e1214614656e106886276a39db5a0b64969e17766b7859d669077d624792e6c963399298ca54af8e0487acad0c5c500e43cf589207b03b86882911071d790073239674f10cac83409747cd5ae38a7eea1b2f640f64891603fa7796e2e396e313f922b3449f8d05d17b1deca87bbaa7867a20a3fa2b1f260f8ee9c14b506cc1c43e1a48892670eab220046bdc9963b64ffb2883febd28cd3f3b2d5425ddfca329ece34c1903cc89e9f04ea228f41573cf544e05df61df66f76eb5f267d4de2fe4b263bc0fecaffdd52f6db13286000e370b9a02d292626bdc26dc11c4742e384f2925edc1b90dbe2a77ddc29bf5143255393efdeebf5dc51faa34b54c55680881afdd66ddb04f622abde25524372a7a459216bccf3029d43c68cf73e20d3fce91308abd0976139a7e2ad7628c2bf3c6890eca351da4c08513169454390018dfe02f70f1d5173e8c9dfe3a7bc7dcba24cc68a9dba1231f29687b60b13d511a30d8b40d3965d80893386637c0ab68a87830e7a925570cfd9cb0d2bb36cbc9adfceaf909a264d498f1643a7e8d0eb0522c960449a48154f8c9b136b13d15d37f82915b8c5a69b5e8d90df37b7732f20584f6c2fc4ed944849ec232f42c27187e421d9decf91dee562aeb8e692227ab76e6cf32a04d52f5f151cbd30bfa5e399691264d09d4f01e1a1cc99d76f13c71b7890b58c56488a36a9bfcfd3ab3c66404e277c882a01cff7af6305ea2a36db29012d9d89bfcb9e26b7038516e50599579f3ca14ff2557e6b87f20dad622e8bf005fdc595fb6260c21e84f6c3cbc57f7e909d7cc9a0b458ee04873388a63b59fbba02f8200ea981be53c0bf2763d324186b965927355a0e6f02546f70df25b311551b2269ed2238c9f4cbe03cf09013ca20bba300924555fa724abd34aea8ed3c4720cc17eda1c333c37636184e614d0788f4c1332da0b61510a11da8a690b8fd54dee2865eb7a88ebf60281070c04b989219bf3439de988245725dce1a3de4330120817c6e4aa524f02cab3b5a76d107c02e59b6f281d9fa539af72ab896afd5766eee0e7156000930847bf5888e56b42680dd63aaf9fe283693087aec72eff28c1178f6d1dbe06fafc650b25196287eb2065a36ce5cdfbbddb4489515d08e0568365caa72881b7ad7b5de932e77c47d72164ec74e087c6807029c5e8472af4f5402f80db5ee108a5a13b529d478b2297c24a101d5b6cab99b6dd2acaccde8cd002796b3e092357560f1f1597ab4bdd12d620be462486ac0b6c28aabd4e16a4f51238a512e4dad3098f979f151cd5ebe19dd17c74d0017b51ab058299451814bf227c4a0a85f9ce0c567d69226427b8c008a3485b9a20d906d1807a15118713921d693a7e263beb462a692480256b9252a66a72623c8e7db1e1f4c1ff11fabe4ad695055d1e0815b23eb4a8f6ee159a767c77c707bd45c8b9eb15fbb876ec67a4c908f579519a5ed17b1786355f0cd7721e03fc0245787d7df22103ff0241faa13952305251924fd7b213c7f3433f564b8a22b2072b70c6d2fa7d3756db1ff34bcc1411774dc81fd96bc06005948fe2d48ddbe59fdc085fffd71008118ebd3fe15629e6e19077e3849ca4033c52da9bd3451ed38f4addd5d01fd056af526657a74565ee7271415b13a165c79f01cad7b96d1f20eff3d1d0a91ca635a923bac0d83fd4678b8695f79c544212e502f868334da3491ee46c86b36b5ee778c738d84392cf5f073fd1c9bf1d34e24626d5e7fccf86206f791cded25dee2fb84da2ee53bd4e491f20639cfc13bcae0364e04ab4c6357629d1c9386b007a12749736607350f0be9bc06deb384e688c0fdbc7ac834429f6b4c30acdc2b99790a9679e59538d4cb72a2fa2fb4f8cdcddb8bc9d07f35c87f663ad30df640aff14c606fad192b74a40f170026dceaa39fc53d5286768935340fc7818285e30a9768d42de9f15d26d36e51a5ce9d593a5241c8b71013ca03efed4206edfc188634c3d9e03664398a3918ea0fb44ab5c4710e8fa634d74defb71b46b6d730bdc8d5a2d27379aeef9bd0fd4a1df0349addb0c0156d024e5f93cef9a2478f8b7b3241dc53a2716ca5559387a4ebcad27a314e9e7bdb7bc59190c9c82bddbb090d6c25093d990a4bb941a931dba136e192936a4901022c5e694309b46c15c7d514ec1181255631178725462887ae25ef4282392d9f8d8b0c0587824bfcd1230dd35f4d5a262d5bd435ee10a879ffdaa591425893f2c09dfc4dff024d7cf4f2a4f9e2b0e539e61d37eff8315cb616650f2bd26cb36b1b5981357eca138878fbdc34685fa303fc1d546584c332d5ede439f6d0ee55651c5e08fab75d285b804bb2e9e22515bf81648acf3cf8d8afcd78a12c010110343090deec08cac17322287efef79c3f3868577f19331a97f484f1c40eb25962f72db3895ee1edcb2b12d99779d8b64aa88b477015d01a1a15f545c5f1e15d43d581bdb340619ea2e0f00bf08dc097dde924a2de804ed8664931ce405eb91051ad44ee255e10a36f05ad0dece6d91f5edeea8c5736ff276742e9a191ff1a14e72c26366b1630cb45df59652d2e9915dc13c8b3200d531f6f6eb1542a4a961152861ea0f86284890c0a04b4195ef51a2053c45b5a9b9c46c2ee91905da035cba655b6c2ec04e5ab6cca3b6cbaaf77ed36afbdbc8a6d6a4d30be075bdd12cdf5c8e25824d730a5cdb7f3dd76fbc9b58824af5433dca05e757457494c9820a5672e389ac76d5cb7d4becade7d1129b92fcddc947430ae4084fb716d3b7363c86f56a9d70ac0878d17ae030a2162c87f916434077d0383e004f56ef601a49e8fcf1f558cc221da775c61f08fe8fc345378ecd441535478b246292cb5b5648e2c154d69a939dd1795a646c0b47c67d28173fb14930294123e0649b774990b7637976ae5899c454f3f64160ec1bd2c84aa0028d120918e8f2a2d169934e763cc5cbaa2ab26e3f56c3825ef2a409582f1cd727600fdf075fcbde7d8f49130dc474b0b683356f166bac3c98eb9c2f267f4c5170672ff004ad281f12cb763f44963f3b10dcc7bf3334ac3cce266fe40fe206a22d5d625e047359b76f3fe03fedd50c779e6d2398189856285f4fbd0b3cce1a2000c14f438d19150aa6b6fdcfeb5cbffeaf462db291db24b245352dead672efa6edb4b7c019be228ae39c607982059b7160905e7cd2a351bcfd8951f570ec8c4ce0cf0b1ff8e395843e468739b827b8e21d24e8527513b995e211bf9aeb9c31d4edf04a0cd9066385c0727fa601edb952ce7651cd8c87be5371f6f8f5469d2a9a2552495821d8122dd5f49c48b9ca2c19525347a00d2d38022aca27594a3b011533501f12b72ffcbb252740c530a75fd7a995ddac5bf16d7aa17a0d5cd8e70d3f47642783ae730725bb66b2e511ffd66bd860785335ddd88c947b5b5ec9b718a9cde117bec64c7a404f94829cf5a66abec7158ecf7fcf3426db683386001f9219d543545eacea5836d51a949aeaf0b4d2521106f6b9d56329fd3605275274b9a9a47ebd1c928c0ba64ddc7a7fc67fad4006e45034610d3d18c441a4f15cb6fca0bd9ff78e744c7747dae99dc59598607a59d66e17afbc8860b1fdac662dc9e6054c236b3fe07b455b4ed6e6749679409d13b25ec7c38e2651642f35d6c17453f8032613959dfb33f42fc473533583fb6283f8e477028a59b5d3b5473419f3c4ef6692ce5083871d8987457bf24635695aee454418376cafd1492fd5f66727ee551de69b7dcef0081fdde538882738e877b7eb6588be662ecc2ccc2bd6875f162d2472e6cc35ea3e99fd6085fff9005663a3d428c458b221a7df3300aa918c86940cadb9b4d431ec6788693ff033ede26939b612dff201f88addc805ee665f12ebaa49ebe8612d6b375cfaf7c5c4b308b385d1629223031b1ccee60957ae5fa094662a7bd204a2d58bc833448848361cc02f4ad0a056b09584fc497dc8b6d557ed3af08a486fec3eec4ee32c090a2c817b5b0b05ef0abf7a49fafed707a70c9bc6115ea69cb9d0cd6963900758cfed161f43c7f57a54ce7afc369be833dfdc33fc5e9ca478249a0107d748e4e78dc7a359b7e9fe44592ad8f42adde225671ffb5c4300ad3978dbdc42e1984d1b6932779a02fab6793f13a965b19926f601c3714bcc99ffc9edd40dc2e8e0bea0fb601c62a525a35aa820ae9b4e1d2f33ff8158d4f068ad3097d3df5bf69350e2937c8cbf0792028e8e9f7a3d65d961c827bc5f72767eb215acd04513215c8840f544f4277aed9f6aa1f128825554afcf15de43f7324f8742e9f07ca8fa1cd6972fe48fdc1b698f33d3c8aa8f66d31b3cb8052fcf75c27eb473070aa65770f18f8068e4af99f7d909a6d59a641ad52490b5ee06e2510d45ee1dc9dbaebb685a39d11902cb9b949530f94242d3b512937e78ae1aca7e58fedffbed3b4e66ad30af98420cca55f2b72f81bd34dd5350909e9e6f5cd8cac7a572bdd86173f4eff8303497e3eafb453601d9193016aaca09ce60978dcedb375854c444a9bd4be8990618d4f213505f30beb14fbe134c462d984cfdeb930dace4d8fdc0008d32192823be1a73946e262e4721ad416913b823494a6a0c776ee52aca57b61f33f7148ccb44edd50dfa74794a8e65416ca21444e0c3080746f1d981bb81e4b12e1b6cc607b267c61469f7edabfdf96ca87181db86fb1efdccda11f6106fffe2d7d29d7841fd9e0ef1a3ce8907ae0e40aa2e6c6d07561c44cc2d4a0601a8854e8cf3d48fe27b564ae81af07774bf7630aedabd8a56af810c7e7b1919413fa87f8f0fcd69efd64ccd69587e40cb288fa66b66baa80f571c287764a99fe6775d047d8412473a4c011ec50ca80d7798b92df0282344cd1d640b744f7a2e7ec9410c68038b62773d33ffd4dd6e59d41403f680f5658282495507efb8a4be4d1bd158b48c697ffe2fe52821b7e23ac18c246dd08f8658122f1bf819a61f42fbdbe86f493d2855d04b8955b6ca39415ed631a2ef0dad5ef1a3d337664e7578e3cfa42cacf186780b9833c2138dd6168e4bfed16e8e834982106cd708634beef38351603ea17d444d8b9f22772f6445da565aa2eccb3dc33222ba3fc117f42ce82623eade23e15fd3233d6d8c3c3178b53e077058a8a1a8b11c163148b36d64fd8d66afb228b6d0c72ff89b1468b0ad1ac41aa889dc49703c7bcaf028bf4d79ab77dca4b22552021cc0d403a7a6f7c33cb3df69d45d955c7ff501ee7aaffd5568e093839c4dda8cdfde2bafda23b8551c196f6ad52a7ce02bbd7f87d5f05e6c761c3d3db71aa82556dc02f28640680094661812a506d2c98e78ed43eeafd5f24d5e92d397e2672303e3df8c8f9269ed8570233c6a939edb14b207d3a7142afb37812d47b7ecfa839d6d2d6e61c28b39b2d2f41a8b586f0e6c2cfed0ff8a4fa669a6298840a781baf911c2d3a0fc055b15b07281b85ee5dc62f3e929c1d4bd92be01d97106f26db316bb4081a2bafe4687d40f0ec98c63009471154d85caa7995fb925ebe48bd340d6d7efb2db90f5ece6a8b39fe96798e38da9920ba7473f6d1db6cb31e8a41e6e1a64a7b98b2b133e831f0834d71c86b29ad97779d68bf911aa730d717a461a7d9cc915c31e6f2fe0a02f99938570bbbaa65ce995904616983b1395bd1fb499bbb9a48a837682a3b4ad6152e32a667dc5541219218d5fba28ce391e03cf28503287cce3081fbdd6dece7b20006cb740766630b3efb9916152002c10fc269dd3c2a28d8205535b1de6826da4b184af89fde78cc98c70e863a0127c6a7d8b9a4047876f2f99fdf560c4c43600b429241388ca6bc1e8bfaa093240fa70930f4957bea724194aea424b1ffd2e9a1ac1c78f61a81c671d39e8669adbff8775cde2f612e2182fe24134474d70cd51f6d7a0a0ffa5d4cb0174c05b72058c36caa935849143de95e2de0728b572aba3f9d182737f2a77a27163fed6eaedd29cf22409e658ca8d616ab64d145ecd9c8114b7b9fcdbe31bcf9702b2263ecf3ae2d2a41910f2c9db38d1323d1d5f364b065eafb0d06d03fb3f9f3f1c8736d1ef72afe661fca60f1512638b349bf5fd861d213d229a77fb0674d93aad11a8b97a721c84808c0f17a7c87f06a13b57f4327e37cda6c6b6291fdb20ce7e44e1e1a331f46f538f6d7c9a0bab2230dd1109c9840d1e2cb58c9229b885860799ecc8acd81a3c4c32c34b378103080840095a73816218093ee982d77d979d668b70abb177b29ea2da8bb5c99beadb36f014f88654d265e851c01a59698ae8c0690d53b0ca41496e01fb1e24f604d6602939c0f22f787d31f06a9327196f8e81c679aa0d35f6983e274229cd704065b018b21c6752962c3b4eb87919ec207c6721b9eebe62a0d57657b7da5c9d557d60b4137e2c4f88a876dd8c350e84dae52a2b0b2e96e331dd898ac55e13715c4faa4de229d18ac32f69465d8b9925fa4f3a94c606edce8e191705ff94132df4f40087108400a2ea226d840b21cce89ba4b23127e6f030573cf5845ad7335a2b6fb1ee9a8def724513c9a4988bc5ae03f9d6d43284003c196b1bd569872b5d851443800f972a07f0be8ab986916b2b706ec05f4f6054d4e2fd7ed1e49cdd9a44dce5e5fd054594ab1fc3aec775e82b6f967b17ff7b2eb1e331c0f5ba65afb26453d354e7c6b9cb124e4b65b72138b9216e470f7aa29b4780d94803252d5a1d700dbe3d2949a3043424d5df14c0672f9df67b72a6a7f26a1885fa29f0291120fac4b5b3ffa72335dbca378ba31e54fa06944a64e6cff1d9b67c4fe301e99aa1f2710229cea702303b5c7e648f245c6a53d98625fd3da078b7f1420dc99c4503160186414d36f1410ad165adb7cde7f67a2e313497478117d12a7ab1bc6e376aa699b989e6cae312719729e12c6f64878afead100b2c9e8cba1ea1ac2b40f3dafec66ab504183de41fa2d3e8142d4566c5d777f330a592a7dea05b73d1edf7856598fecbbc747c287ac7fbf3fa72e3b520aa3f88b2b5045f9ade329bdb98dd8071a4ea012a632e468b5f85c2b8a3d91136dc340a06030fe2e68bfb1c05dc93d2c3444ed7a56b746c2e1c99138399393507215a0a192bf74d5d88f7106cbbf787e4bbdbd59b1c1f05d25301260a9aebeba7184e23dfc302de53ddcf8a7a19a08e296439d11feb743e9b2b547b6d954d076277848b5ece39465fc15520b2d6efc95cf5cd47d888df134a8bd568171a6de9b776d83a741950aa546b3186721f1ae76023448123c20167aa246c9c302c34e3bbb66a6d837c042b3edba844b5f02034d89b21661234063a9b58556e57933d6c89241a7ebdd8cbc263a31cc57e6809c6fc9b3340b6a8678f4b457f01ab7ae816468d4448c83c41fbab6b7de0e20ba73b4437e19b07e23f43119419d30c3ee09e8811eb1bb7a3c532c9941ad854514c999b1202b3d8c61a23304972cfa9e59abce601bb46c0eaba5db3afd24190a8a05d84aa3b0a357f2d73ae3a9f08f85a5d7eedd3742fc8c85083a97db59d08293cc030cfd67b19197b5fa14a555d043da33ec7471f67f988e6f336f90a37174c7348adc6c241fc4a1a9d4ebf93c08f7095a8768441946faa3777c34c53ebe2373c9307738e1594666cf716128c7e3f34a3c4f75c9cfcf4fd9c3f063df0614f051eeed7b89df937f988bf4ada1fe119435d511ac93095f844e5d2f97a22fdd62d7771ed8175c3dbce8f42f855775ab977e12559664166c98ff00c1088186844b6586252571624849f6413ed8b71bcc2e5715eca3b9289484cafe9db8a2592b317a06a2cb08ccbcd2cc2b38d6412585c489f04d3919090169125f7faf2f30a0f3c89915ed642cefba045c6fbe26866b82a70456a211deae01cbfcefd315fced0d3a85b5ee39901c088280fc692661701d2c8e4894d31f80f92cf1074a667702cf1f451140bda2146ac0c57307351347def5fc98966912d36f570701fad266dacbea4c2106c110acca0cdd829a085e5f7393e7dd683e5427fd894bdae6f8868503c847468112d0974cc75ece3983329a6efc91a27eb7a59c192fc57e5c0a79841d728d9cc93c37f5b61b1c1e7744fd120da139f1e828d29a5b012ee2d4ff30f22b74e34559a0740e3a8ac30eb5420606b59354d092aed2545030215e77f23e297997f983f54e4d265ab8a9a928b30a27c2de7d9c488220b7f74c1afe01babcf7dcb1d4145c3a86b910716fcb7cf4307c390571a21be763dc689651ed9ec4493c89529ecf67d89f362bb123d377daf4397c53ecbf163d15e9989f40ce4cb2ba4fb3d279f4a7b4f74bf071cead5868244e83863766516676d2a2d5a58be4dcda19676a17b13a4f371ddb5d960747c4df1e48463c121149703828b7a5bc5a057362d299e59fb2fd265c356f1673d8a4203c99f76f7d01102e8e8a4679522e8f14e81e4f7e46327c0c4c860e237b8cb10126eb358236adef23d68f453a546943072434407fb501c9b0d51c8541583e8cd80489f23dce7e5cc54ef9fcec4cab6bee7d4305a2c6352a909c717e2cf9a0b4b7e636b0842aaaf096bb677063688eaf622960a13bbfccaec7a4b02c2b1e285aa77d3bca25bff3bc76aa67dab2b2cf50253ef3b0a5dcc9501ce2d41762070f88bc8ee62cbf478bb1253d3d7b6f7e1b9f5bd2aa5d65dfbe3a0fc798d63199e3839fc190a2afa1ba4c2d2c6e8ea29d9bf9db9514f60d8c5de0b7bbb1adb931ce9c5406136ae6fc6ffead632ebf8888bd2cf1e43a064b899dd1df149b377db771c29be44a2f95800c9b3cbf4c3a97df8ee71974e7a11cf71a2959775c651fe640aa4abdb57ebf61a348266feb58e4ecafc4478a32ab97362b57c4c84b165fcbc60ea939335ca92fb83a2981a09940d49da8bbbdd53ef74d07fb98ddf98fb26a228e81399317131c755041ae5f4df3c5f1fefe3937d04db3457614703ff1ed66c32eb27ce127a332a871942409178a4ce2fb8c1bba873412d4dba91a830d9eae18ae5bbf7bf7598f0a78af9ecd86a24b0d88a5b03168009ac0337a057208ea56062f5c426d5b52d3551aa93c1f2f2832acb073a6aa2607bfab4774fb2214ff048dfcaf723fea718c1b742e8fe768eb5df801658edc7f55d4f1c473c2478e451e8db3ede9ac7bba263384f9e0decc129924387018690c83ef03dddf02e70a7f21b5e962785f746c006ccf5832c1848662b33ed842203a449d0e5c39b7dedd84da5eff649795ba5faad37382ab61af98e1ae4f5fb861e6ce7e601b0f25c013cb033a29a11336024813e0e62436916f341bacdeb06639a8425b8f034ad9a1a1e0111d1d595d8423dcde40f81bedcf6f7670d4aae064ac4b5cc0f3dcbaede829cadc7308df60821b7286fa871b2ef6389b482506b359f60ae98515bb14552daa24e50d225647c4d0cb24e495f8d2b059c5fdd8639332a8be38d2c0f93574b334681fe99be3f1c038e6ee0a034b308899fc1308975ade3b84bff89d4d37a973aa934f6b5ea80776993f67554190865d2d9ca2bea99923972eb5de1d6e8ad47fb4ec9bbffcf2d7656a5269475c16b8a079d97165d67831edc44d751c8a4ad0870cce22a2518bb386239883da8a877cd0fcf0f5ad36a6dd4973dfde7bce9ead1487965acf3152302eac07c17c4d6b07b49770f9959a83be8ad2149058770af91703352264343398d0638a849fcbb0fdc01c509eff9613f57bf4b3046107e19973d9303c355290ca24eb512dbec6f48ac4e9d6ea72c275a47b1ddda220f5f3590d16fac22503f494eab6f69085794cc809bc59614b18ca8266e7a624f21467b066a5189c41863beac69125b337da3a4d8e4114297da63b64928682ca0662520d7da6dac4c6d424ffa1b76b37f4696fa3e5f978963a3b9122d7b92ddf5df75a450e8036340f3dced85ed843a31f3af4f67f0bc7a192f760d8a8b4b6249189dbf85d047164e4f135f464498ede1f0d8c9d5ad85ccb84a8c88dc73c8f82e5a3fdb32f05fc12797b692378b147bffaeaeed8fd685e6c855ccbb184f795eff88c31ba6cca2205dab235a7e336a7f97bbeb64dcdd725d61190f5115e303bca334e9ba9763d8cd4a0f21e0d11dcdc4802168f978e1d4f08d2f0fb58bcb40da3466d4291f665c22847f6d1d2a47bf7872a7e61e4f01c1b1441e271e26cf300419ac010a747126ef68ef043fe011475e25c3d60595848b0755f566b74cae521779ae10b5b48cf236f456a3e64e8627abac6d1b02c38f9077c741ed789ae66b25e7d6993e81798642e8a90f3246f25481bfea44ba4fde0ecaf398a74bc22c6857abc379e2631b521cad71ca0378d87b74378ca18270267c372d9eebaa1e46ba2ee643f55c49aed49a0fd9147d9dd690f3c0fde2a1e99e5cb45dbe16c904422257381b7dbf59702c3cfc47a043dc76f65c827095ef7823e667c8120aa70bf175b9b4a5308e9d272e7f126799957d13374f001e3403dc4a33bb44e593b5ff5ab11305dfb920b88a64ca620ffc7fde13ec7d90e1c75f44f186b1f0a8bbca8d5eecbadf7245608752d13251f1b2866fc1489d5e7a1ba7c11db14b4e53a8fe4f590361dc99dc568d0308fa8e172514ba5816ab1fc2a678529e03318cd5f41267a694c4c6d07ea43ea4abd8e8107e0ac1c2231c844aa3b8dd1378e8cd89810b1d0371d4a661b79419d0e4155e8c6368d76a5b76aef978c8e1145844d9f9b765c8fc1e34ccd086e37214cc9b0167fba26340244fe906fdf76da93574db1c009388f140431f61ffbce5dd29a5e05beb63b608df9f18d1f10f9ed01d4ce2e6904ca31628b4e9c64725a54c477d4290f1b98fe212cae283b90400a6e632aa6f7e910f9ece75406fcf977c7ac01f6a21339b2ee026c85ff8e7c64d8bcb5f7b6104eb4abdd238125a79f11ac0c888a32c4a1f957f16a76892ce9ec189516c8019b2d8f65bedf0f88f52c39273691b7f697b7350b16a5bcaf777c19cf483ef284e29f2d7bb8cea4a8bf05bdf779ae1cebe0c618e0932db8cecaea1e484ee12c8a0c41027141f22d40a362761d15436aca81614bcf5548252b0c701abe74da1f43a137412d01e98ef12accaa22cf4cc9e412bf1c80c5050e98b0f3f5cd614c252d4c296f29e1e269391fab121958d3e710d648d4b4f44715f83e4877f7c9104f2fc4364ab3c624d36a9800fbe7a30f3df1c5f785d9e9650af03c338d53ae2da6e2d10657ad883fe7591a5f7d8ab7484a750b9034c2e9be0510368fd0d867d4f6f796190b4de27f92d51697b0cd7c44ed136ce2d411a6170dbda6bfdea50ed1663864fb04f462840b7e50b55a93a587dadd5f9a5e77ce73cf5f242a91d392ab2f452425c99f660ad12a99f27886c671338705b12fa328a3d6facd5bb9db6cb76b2cbeed07a48a1d535b126184006ec1892ed4bc1eb1c0b697c1740459cbccd3b66a6f8d6ddb4906d64e2c96706df91236b284dcfde6717a47aae8484e1aa8599899f39d48823619282d4d1720d15908ef0c2cbc006b7a578cde1d68f9a804c6c830af6bc2fdd5d38bb9a8b9f33f95ed29c103439d5b1eba7f64226f80d75048f1f815345cad5034eb9e3a2eab5063c4a5ce40a1e419a2e25bfa6fc56db8c3d7aaf044ab18b4416171722abf6cd0836b75cbc0cff6091350cb6ddfcd21113ea036b847b6f75e4ed132efa7e54ead3d4f192a5c2eaa3c36b0aeb9ab68448a13bca5247c642f35bf884c91afc486d0689822aee0cc872578ebef209df46c726a13aa68d7c862c0c188340559e3918ad36cc70b52a2ede2880315a2f9e960db83b17129e1450cac17e60ca1c03649ee5c9ad8968d87b3559a56a873d5975f257d905300f28142e1683e1366fbbe8346bd9cc05cd2981c7ab9f64c9ee4a218f8d4f6d3522e8ee2466fec7f6d2b1908a30b91b88d2b45e10cbefc31293a9d6e3d9431f86319e28073ed29865546f0363698aeda50799c487037fcea19ee9dbcbfdcda825e192aa71aa35565ff2129a87d160de14315720a0c67a8e3d45809f68f64fe8347fbe2be8f88133bf25ecc40a58bc39f4b70077f7017275fbbf2b22c48fd9c6fa00a31d9d36c17a5b60c5ae73cdae653cf76ea2bfbde9590056e39d0ebd2b4d19683f5a2197e3124bffbd92f4d9969a5a054df000bb3f58fed5dce032280bee21bae3cc0d59e08a9b67c552fee8985b906784a80b1d859cc9b53d0a52ed9fe5051682625fdbb451e439d210ae56d166e77f4d542ae881803fe76fbd31e7b475e2c91f4d494d48d68941ad77761d3ecff9a1ff2f453104092bbdce50f77f95831de626de8754c5e13d8bc18bebb7d6a1a8c4b4147e9ad176002a7964b494c156a29823c4494c66bd0e091553a885c60fb97083e4fae43ca226da913a5e988839a58fbfd343c1f4573306afb8540c7407fe068e9cd6554d1ee3793b3cbefccb5bdd51c958d8d5a33ff43d7ded4afaf07e1b48685649325727929accaea12b7dd5700a6fe7d0699d337aaa751c04a08bc651c3552acf83608514e161ee23d9824921ed19f302eabfe349b695dc0876081e2657393d6b33110d4794a8003e7e54a3e24119434a3084b977d165996c4b66d088313d8506c4b8bcb84ddc94786d39d844e300bc77cc9220256a6e8149422eb7f0f498d60fd5fbad678526890bf952941ce237ad5370202ec6af00d2a06e5c2f5474c0a50104aa9f09ff0385ca42168b21944efea0414ef63d47f7b78544c22f7a53e518e02f21e385101fa41cebdfef9b76af1e990f6c5b278a94dfc64458529802fd0b8f44817f33c75c852fe8dc60d2ac11e1c492759d1cc724fe46a3b9da96acf039d8723213d120adacd83ee90e560b4f068841d3eaded3c7818e25e5486d0fdfad0185695763ad81613a4f250b45e7eb86f00fe3482159fac2176ef0670122d6c6601dcf6500b1933211f059d9fb9ff1ff2b1995d84d56e8b456585af4aab77451a79340c68aeccd149bc3517c8fec64cdaa6297bc748687f76593ca1154d948de43d253e138d44199153dfe0db29d0e0bfa29b8689ffed9b173794f455d38ca13e3dc46999a1bd939a936a5c6d358546568a6011d160a138d73d86f3d5c4677f1e22d6b8471d0a52c2c911442d8478cd8765cf4a21911c32851feea6071ce95b7cc751e34c78908b2660c8a167c999e8c42cac3d5c3b7cbad8da1ea4aee167ed57cd5dd9ccfedb36b8bbe0574485cb5e8f8d798c7357bf6d810c29785b78360c7c779c512acca2cef787a6be4df780c9cb6eca67e17bf4b11a9226901b867c1cb5ccc8cf55989d48a44f4ee0cd903719794b8e087b9c15d5cf5268770962c73140316e6913903efed456c8649a94d7fcd023dac66e06c8d4bf94dec2c827f7204e4505afd6c56d89de7de3d360d3173f92ad57c88c549950105623678f4527c83254dca895ee85cdd26e354af282882dd30a7b1e999e2b6bfae4f777d31693c7890955321a9ac6e5176fa6e570ac8a8561fcd63adcd141a625cc9260b404a9848f3cfa6d373ffc56873d841fb28fd6d7751e2393ded7cf5e26a3591ff9e1259e414ed4fe5b9cecaecbadb564ab87828eb6a8431ab83f154d435e456667e2f1f8e4796c6560fd6f23fa5fbd70a60180e4366910ae2cab0f97c4a0d2d9487f6e885fc4ac43c09c9222745fd8eb7102f2efa536a24adad1d3454660feacc3548797da59946e6168df62fa0136ed3126badc5a1ef84fb08b3a3db3d58d626478fe37433403bfd1d58e187547fd728f05e9f797ee492ef0d3e73575ae6c9f31a24b4f306dac9a4268da290b236698525112da76e5f58f76514e2ac964a280711857455b2fe9d6989e5f3c454e0107eb065160218f10aff60c3e17ea115e612d555dffef43d45b57926dab13580a732acf6cc372217aa48c30bfafdfd6520d3b87885de7bef634dc6602751a640a1f0e30e6c2dbd240c7eb625181449b45ee22b6f72decb4a3bacd5e8cdaf6fa73e8147777462c3ff13bdd60780332de94c9b19500e15b57906596f4edaa26bcc7906f84cbdb944ac08924b2b958d8dfb98b083843b56f239b40f3ecc5bfd67b0ed0107d04741abf8395256935f54e8c32a37dca768ea3e734667fbd5f2046dfc41770706c1a454978628b9c2cab1498f129eaaea09dac0a98103e3c2701c01c53b05d2dd1774282296542a868de887e62dc1aef8068c75ba46ec3ef60b14af7be1e38a823ce6cb6c42e1875b96cfedd97cf9e8a1690b2b9e17ff7ba373f7db2078a1f996b4015738a982da2e9244b93f0398440dd4d937e2dc0d0f6fddee11300fe35df9670b514097e1f847847b83353daa6f27d5204730ecdea26b821303a1f45a0c76655a79bdae647077d605a209fb121bb1d7220346afc5d3204d46bf6419c7a7165fcb9708af17c8ef76ba4407ac73cd009ec0c8dbecd910a89ba8485e39db9b2505149b488248a05eb3334e1853f38182bd9d338d4ff5f624283ea68c83d5a53a0821ae5312dfef0be797be93cb0c5b603b5ab847e624d2eec65278e7777368e59bfe3cab2d4852930b081c7fa9cd7a0b29c2140680fd314f9c467af219fabebcff1b0d5f951e9baf7ae98292ae69a203d69b0e83594b89d997bf0c2230f1d2d628b19042101de7740455afd205ec9392bea1b0f3f8dd78db8d7289b1154a1c0c1b4a17c8f42b0433cc6cb920207c864789d570d6cfc2f0c1173afbf1100e2ff747a10e829416c3f4a8fde92ed649b772c821580a536c862c9b53877bdab320640ad9d90b7e8fae31a429b046f72484bc9e933606123530e182c8528a5b53f4724f70461f5b871b7832892484e7507a7a9d865c9b45e3dfa88243a2683956e96488873f7a2f96eb2f5d5a99993d456619d00877029d66a271b17cd42273bf895ff9c08479a6fe6ca3401c1dab62323ee04ccb9eb0a391a7991feed8324ea935d9fdb1ed0a986d7246a13f156c36677bec86b005459c5598b7da8a37001a7226c567af8411c712c33895d36a6acbc0ece3654aa3fa3c6a98582d6d2183fe991b7309d82e8e3eba997e1df1164f71e6fa56c8a58028e528cf9b173c48383857b3fe94860dc2681f805cfe4e89fc06c6e8fedf1c9f33c068591385af822b350e51005c467bf1981690a98f68c78684bc2724ae3207ff531f33f6a900fb4c9df77293bae14cec44ad0e486cf6dcd977ca6b84186b34b880cd4fb31ca12c6656b17a279bf890ee1f52cff462b577cabfc7a47d196965473436b9fa508e7ce81cd12b456f0898dcaa9edd52ce59581301556f6e787004c199c8d25da5c4de17ed4da1dec48428903701527fa3f79737ab3f10d02f1717a6f35e8ec46aa46024da66c3e148687464eb97813096e0eb95474c51378f999ba9d45f4e8842b850c3a96be8faf6fc94c209460d4c13c6e91e11fb508fa2c7b798c9ec7a1e3138fc90c24cf959a28f1a9cfeae8af874c749d8ee432c9375b2666215eaca7361e1728e5079af01251c5bec5b3e62b252405a5bc1a407545f1663f9095160b212f7c4721ad3a37a8a896a3af1bf37ee21ce5f2e41025245d98f4ffd991ada5aed1b2b275dbb568e099c606b6f8e79bd0e1473683a9f68d200e9b6f3b30a5c152a86a53afe21002de41ae70af18d5550ce4cc53598a05f7b383e296b7091ebb1478a719acba5e6db0e0b97bff19830e473b6a7c8c29691f6019834a2c4da2e64c264fa3dad2f2adeeda982cb43a8199dff671174d224882ae3a93202b26474fdbb0ca4d57e9f741118d97b86e1410fa104459a71cdeec3291448bfc4a01122f391193a6bf8b01364eb7ba9865bd48c64a01162c8d76c2084e197e561d6b2ad11f6f49981595efc8f6361214cf42c3ed0e257376dd328fb6c1eb34957450740dd5788aa79774a266d5cee2b62291968c6a04e12a3bb34b74a807702a5728df09d737e5365f5a783aeb21ce738064f340cfb41eaf572e9416e002b6538b739044e6e3c861d74a4d1c1f8711a8cec58f9938788a29b8a51ab213ec11e0f3e2f69e878c788a0b6e5ab84d351d9b05eb92f8e6032d33602dd033ea81c52851d2949768d22942cc0c2f535eb9cf7438664ea80b6f2f49ddf9ee3e2469b444824b720561aff9e3b45e50b524cac41bdb93c306cfb57f11428f93b1dc3ec8fa935294afc5f9457f92cc870244587d9f9f9a7521786e56de9294d9efb26746285f9915f061e873dc9bc9fefb74cbb0ab847d2cd976420c6d5ff3f48fc0e8cb03fedec63133a3bba9c2ec1067ef72d3212dad7661d307871c17cdc78f04548955dfdcb035ec0bcfe5634bbab5b2de640e6dd94d50bcb28ab22ddaf214798bc7d75cf1bc0885fdb612c946320491c18fc611758573f7d8f18ff747965e06ef4345073d72cc84d52bac2e5ddd1fb8fd1d87a9a102adfb479d2f5679d8c9b169f47c741a37bee8c5ec3f532e65383b8c72bfd30ff29f00932bfb2b59263b930927ce23da02d7fb0a9e768ef6c6dc34d8c3ab948e8ca90e229643cd003a210daf0139e9cc13aa65290b3e97bdab53d7debf31e7c871a075754565c65b262dee0eebf223215eb9a8c72ec76ee59758a05d3df23ef8798ed839cdd1788b5458b35f83a11131352e7bdc32b5fc08e59a928d4d619df0505c0ec556a7768916e4504a1fa94b9569c88e635c0861786797b9c6ed141c49d5ec6757fd60b85f744a968941def2616f04fe7383397fb47677c826354f1ccf1b577be7258ec12e391bc49e8da3f5082c89f034d8f41c2d134ca3df58c3dc990cd975deaf58a3dc9be539f6a23f73e392d494856bb165040fddb097d04d50be6370b2d2bd481f8e6313136298321941e7a588343cacad3095acefd9f7e021f5daaed50a84d2bc77d0f256d403c80469962af2b5bc6804e736b745f93680bb84ada4b77bc51faa2a086a816a9922ce34fabb598d16f7105e3d580de11b9810bb4ff3e802ef772a075c52cd3cd41ea3e23358d4f728265036f18a59410dd429113070e65de3c66efd337e8709ee55e33eacf7784441b4644926368f26eb1e574e64460226e1a446c713e7ebe6f7b69b7a93bc2a7ae90a894b30f6cb6c6ceb920859b35514092f7c76c5a8ab4a819f8186c520d73134dc823d3a31a5911fbae883e7bccb19b6cc581b3f36f3752ffdd53cc344339ebe26798b007bfd848922a15591275df5b1fc7e76e739a9d54d7c84919fa3ea1e9984968161bbb236af9fbe4a7d9b4cfbeeb93eb115220cf82cf1b4bd92f56727580f0b19ad712c74b6116f2b8bcb19e3fceb568af6158ee3cb5de78b0a8667f4c11468f7d1c861643642420efe2fd4270ea90bdf99f791baad9fc71aa2a48d411a98e069682a6ab5ad0ffd51bfb9d39bc0769dfe297804c6e83145696cc58ea7235336a65db048ae214efb1b65488b5d7f99511ede5c840a1b8fafcfd598ece79d6e55e87f54a42392f861cb722395234533c2c9a87d0e9cd3dc029e7608ad72aa53848ce3b6e9ee0fba7a257d3c56d64ef432938939fe9c6abf96dee788ddc88bc87ebc619b20ae0202cd034379eb7dae84332038445c9ad9221c409b62516571817fb3816b1568994c0ca026b3b8f32d93a362cb11cd650ce32c53a0599d02a732bd98b9c821973eb04ca6f857cadc42f00d89425041ebbfe9c15480e628e73db29bdb3bfb7e36da0e143e0a9ee4011958c868b6a7729321266178b54e072f2a38d06454d6585524d4f5b771988085a82340ce58319567ba787986fef04f01e311ca401b741b3280387876963ebbc827d17051ed585b542c3d5f549abf1ac989be3e1fc7feed388950f483c895e1417da7fccc238b179a122085feb464f2b1e272bbb1b115a75c3aa636e3a4abb16e7b53422cffcf1755578f4c75cd6e8aa611ff39da09fac18aa7970e54936a9df1f9bd0c3ed27fbb48229ad1211095e666e58967be4288a70be3c9d290d207b1d10f2bcbed872fc8c8eda1df124c935655de9495640fdf32d5a1a4b19eaca595c3ac28745690cab0ecf2bfbd6b1e8092cb6e57aa935a68d7a16312a30e74b2aaacb7933705855b1db0ec67e366870bbe164e7d56c0c54006c4cf25b85987f583506c7014545c43a5e7d9a605bf323bdfab02b6fffefe49bbb27810a620a3b46fa24539af7bf3cc6addaaca9f73705f0c7b2de2bfff3bd22892af1981847384096fe35f25fae2f84ea3abafa9c47c5987a82ede83473ae43c059e270bd08992e532ecd8dc836186738870fe15885c0e32514d116de0914432c48d6764c717a0dfe79056d145b79a1339965b6996c2501d6fb03ffe5887531eb1cc57a383d96e96eb61ad1780f3b626e67833b5b9c7e1be9aafa814c9b0588308bca63177092a61752478fe66ee3a1332c55c80654077e0a602394606ae3460c21769679a94d3fa55e42f8427d70979e74c7645d4688c3cab8269a5771a87b10c2cfb3c2e3670cccb28be81c0118bb76bbc4268d2693c353d78ebc20266144cf9ee158dd2c57c9bd7062bbe62a963acb32292a7cb1166f0fe0c3048d9ee77e9b0c2c50e1697ae1567178260f52e764d72c1f7458ca67e06ca65141c9aaa2701a23bf041e16225acb33fc45c36935d2b80dfa7c8fb85612de36d4163aa11859a952adef34cbee8f1ee136a679e588db2a36c8d7ff4903c130299ea7e2214534652d34994cbb2240318fef50513434bf7fee9c59430662a1088bff9bd12b7a21b5833baca286cb48ed4df0cb0dbb1e074855239823c6a48f179b3dd07229699fa2e75163d8fdfeaff1479f0680f8221f5512e023b8c63002a1e196603e4aef51064ab5b4a67965a50061fc0ea7ddeef94b0d0802c0c91f1060479a965033588d46fd0f0495efd3ca09c4f87ef0b3d357e04bbe38f6efa9ee623c8e04dd509122369efae4ae4e0611fbc7a65f771ee2fd1f789f261c6dcdb9f7dbc6e64f3e6decc5e12d44dd5886a16302377e48c3125a05a9c13ffe5b2431f30c3c2a2baadaefd54d00012a78e75e2eee8e9732ebe1c982607586e378fc3ac3adc00b4696983b2b921b697183942b39275f801ebb015f519105bca085d52573e8ef5f4cc4875a69bfa459eb7e8dd886df73e9b27c56c844efd1a78f82194d04875c3737828c14bf32d46196034a0ad653f575e1dd40c428e505388f171f15428e2925104046e108cc104387e9b1ceb3cb4541ff4067e23367b5ca274bfa123bc7193901fea29c0fb614ffdd63f188c4cb28ad52db024a2c78ea157258ae776f9adc891993e6465fa1118c6fb99a9b9882be3a22a91569606cd57ce0fa4ee21270e40d831e619956380a96b9a7d90c4a3cb57422fe9749c48242d2c4eaf93bd897cda8b61109469627eb881a0e4aa8f250ccea9664fbf43bc74a2f10a6e1fa459843a73f236d7db2aed4ae5d9af0c5dbd598dcfa32854f82e7aace2dd75f6ac85d1fa4e1d59945d126ebe276259153e5c420b9095506b4d1cc58783537107e8bcc60edd40d7c2c9f45620273575b25bac234ec224f89178812c4bcf7b33c6681a44d3d20d8a8ac3822affb56a6e3e59b9621d9ce862354f67c5b9b4b042de42e20897c4e1702589862e2ce3455b6cff510e8c3c68cac4a68c4bd89ba6eb197474c324eacceec105fa050bed91d180726351f077be3513ccd273627c7e6f3584c747a836ab581c19e452fbd938e79ae6fd56f46cabcf1eca719d03e7b5bdd80377bf595828276685890e249493fd6bad2de47e133b6da7376b7e1ef864f788eb5fdd69d85728cdebb0cc11a7d9d0965042105fdb489fa3fb87435fc5a329cac95e53547976aab10c758ee66cf640b4fa805632b5f2f546995761961ffdc3224b5b81ca7abccfba7b02b9cd8865832e3ddd31c14701b8b87e73f4e9afa87cf0afee180b48ccace841331e3bbfd229cf77bdd79b1538a0408f1d31204e6e02035d343d89aa61a6a5cdb012a5dd15e8dce59737a43f3249d434493f1fcc0c2ebd5cc76fd0879b795bbe5835a54a263d59af211c4d942720fc30af9e40cbd7ea6fb2c9faa7695ff652ad2d3e1a47f1e2d036be8bfdcc5f44f8237d8a30e943a4fa10b7912382b02546dd4b33d46443628fdbfb34827114870acee50391ef9b2e44e8687545b989ae6dbb672a3dc9590f43a524ac9e9e9496202594eab2a58a1e944b1bebbcd5e7bee8f506aeeeb780bc3c1a75fb01f310f4f7f0aeefacdc2d5f447649f75a189aefb1ec08f8cf897165e272dfacdafb8da7261c7156bb920d99b0543cb77f0d23ea95f7c1ec06f7b76be79610269ad2c39153e4fb033a4e9ff0669428975826ab4b835d9b82e757fb2f07f2070048c6d8d2ba9bc258efd5e64b20d73b47e78c02dcfb628e555607e7edd6c51061f63de8c795fd95a8235057acfd7d5d5cfc410f989d01eb8d65649ff3317db61f7aec121d03564717361e77e8e19ac7cb887cb8e2cb5cc08379bf72f51b95f788aecfe3028a4a5909aef6c4da381f07db453266dbbf5720a105e2df116fc2c77490bba4316bafddd247171b55d4a2844d0e651e27541294df3b44a0d3c98e27717352fc4a65ab2d38732fe9c32691ddb994f6c4dcc4bd1d5401cf4f317a1e94a493a615b3793579033b5d61bdb141d77c9d795791f84a6a7a341559b687f5a99e83bbbdeb2e1eb237bb5741f5629e8aa9506f7458e957985ad15402ad59ac823f3f4a25b7b181f56e6c040b7c6e5e32a225798f29db73af8cf7c9fa142fccb26fb560db5cf9ca38eea5cc4a4b29514559e0a0fc677fe2ebb27906f63eea64a0270b7efff894176cef5a83aec6bf3d92bc673e8b2b040e103b3bb506bcc05a3c2bfca39b61eca9f0b3dcc8afa2d295c1c80447121d34bdaa00851c63f035423c43d0cbb1144cbe39d83235205f53a06277411462e45c6f5be663a7faf48d5f3ee68be95bd5f8ad7e647435f49ecb81e3345ea4de7801be1e0c7f52169ddaf612dcd4206292ecf3be51cddb035c95fd542770c4c96c1ec0cb681b9d843e35e2bfd7310eb8f336069d0a358eb93962d79d53d50de369fb8a5d97ef2c74159b8690e88c72734e0d6b575151a3820057ff8e8cf7403397b983242f814523c81e8537b8b2731a0b862c45d24679a1dc3b3bd8287702fe0c29e95db346f7b771cc5df729065bfdbe3c11c24bbd7a1d3eb0faaa984709a09230ea5b55df0a9a749e6eaa86feb9a2b3a1f8debe5ce4cf654ee4ecfa88d3d426d263b7e3f4b5b547157c16374e99bbf9812500a78a3459653a01d5dbbef16b5091e989d7353cb49adff599a4eecce5be7ee500012bee622c6dee13b7d4f8e379fa7c95674cc30bbefaa4ecf1a2fd7e755a06c2d22103b86fef2d4b3eab5867b42bd74adc23b04172fbfb88698ba68d8ddee224c4313293b6dc0245f9edd10886890741ef47fcc70a9a521dc94903d8d3a20fee90eea4c3dd0c85f4cc80a16b7e761c6156864cfe4a398081a78d4642d44db80ceebc32913fa56526ef2f70a82b7f2ac12f582407071e346b3cd8832b9c85ba563a9ecf0b10a3f54f920c972dbcc1d7c5196b9e05b764579cb3ec66cd8b8eeb397c9a74bf57f788acc94d4fff749db63dc2ee8572d5637129c3f0c0f97d7821022cc2739712c931dd494958d7c3cea23c2cded1702982ef1e29d90793dd67948d16bb2da649bdebd2215a2c73e0c592e54f883cfc4560dd001fbb449c214958f534703cf50a79c54a8d429a058fc3c22d0a9f30f29d829b95e079c1ecd10e0a82326b235a82bf11402543bdd49bb0f3ab3a9d2144ed3910d1f1643d9605aecb29cfe8ba9c810f694743a2db0a06a1088e7d59722cfcef3ce9c384e011f5dbb4092c6481a9c7b7c29946a7e280df469998065d6502db06fccb890ed70b981d038ab63e6f47efe9202dfdeaf5768d72d7db636e67a3c081e5792444fba3fd80438514a9f24d73334179d28e060d37a7fa5f15e5e0e8c4c0eb9c4fee57819d7f820424a5705e3ee2af6ef3e03996411543ad9187c9ce2b6c95390c00d5b4a19d06cb774665f818d0c6fba5e5ed76b5bc0d16616cd817446d793dc186f95de661b2ba986c8b89c9a6ed45dfe4b99ed5be97d3df44bbba4fb5b0a5c200d283db7f7ed7e8b5e42c66ac75070d5c843ce2bc72a8f9c914860d86e1081b93be3859b27d265973697baa27c7529c1a0f0f758cd11a9b610df9936d904e7d6b44997646a48d96fa0bb562f0b481d0409c907ee24e8bc5bcdfa9bdcf5178d7d30707e77df5c6542d6ed083e810ceb79efaa6e9b953072ed82a0799d178840b8bb5e5ead10146a4fc1e6230602483f377eba717456a5801ca7bf62cc747ad16048d367e4a1363183a99f3ec5f0dd9a5ac4a7d8bed62f237cec4d7d0018457bc4e36c9c3326a6266105eaf06e7dd4e9f9c883fe2c0b36fb25573d5ae68db81753aca07eb046340db851df88511fea58a47374e330f1c4183f09c071292817de914cb1bdc0c1111d3dee587b495cc66efda21ff3aa5cf5f6eabc40508673c9834bfade31472af31138cb5a367cb5888f483fa02d8dec1fa832a714c9708e278f690e07d67ec8534ede9da8914cdb4022d89321141c887a562eb68f46f2735bbe7570adf0220a8fb99baa99cb14153cdc61db30218ad7826082006275fc847f248daaf9106e120a34669e3b2d9f4b70c18e10dbb1c17f3d239fcc54ddefef0012c4cf4ba7ff2163fb8947d9031ae84cbed85347b97e4849ce173bc8081d050d4cf53e69048a6aaaf5c7d62af10e3962fd1902fa8dfa4f90642e1d7c8cc4e6242a0aba5c7ab6ecb5535e33567d779a2de4050b2557726bd45eca8423719d0eca8b5ef216ff77689023a6b1d302dd08bc2b63796e3f9366de635259f30e28572f1967ed580a731a443ca975451ecd06bd6c56d649ddf693553c6baa19a28d5e40eb226cf61deab2808c5a5cdf90f1d6f6fa1e901a2887ce98b90e492a395bba9ee2eda1922732a1837add35d470e6d2f44dbc61398f6dd882e87902f336b0cd16c8b8c738310513d261568ee4ed16ac57f63ad942af1dd24f9db04316811a485e107496622a5fc720ca5277b6f40edd321d9bea9a59b2f34d5ad8ec6f81182af603ddda5b0d1bb5d37dc7e2a3a57c13f6351b5125671a0c72d63cea536dfcfdaa66fbfc78dc834431c390921e367c24b7d0dc8b644307e6195c1dd49e6fed2dc72ee1daa7f734640dc0780c93983f9e02a3152d6a03d15fbbf8ac0d4bb8fbc2a1e525e8b86332863d397afb54be035b02de5ec8eef0f0c3eee6e0f47895103cf153865e67df66873046bf0b9828ed121e34f1a7f943cdccd70290ebcf99f875171c0653c9d2de3637d3ed792966b31e3431fba40e285e27c2bc2cdc99eb652f563e69ba6fa2b8f0804e7aef920be3d941342f66799c22b120aa0fc07da3654b67a99fca514df769369883b02f1694e876d4983fb8d7e3dea539379008d578f768635b529748b4f4a08c8681495dd4ded9b256df9e98f4bd506e6f0da3eab1f62dd21f5a900e1c74160ca45a2539e9bf961ee33e9d9e0e33b1933a0b50ae415d283d46d7c415a942f6be5471fb8c04e8f1e32ecadd5ad47d350b04a7754db802765436e008632e2a84907b2cc3b4b862e2e432d003d2cc6b2a9b986b9c47497d395f7679d3543d54a53661c5ea79f11804aaa9761774ec2d70b940d48a1cebd4b6d76747d1e28b08f7e73bc727293cbd7acfb7df670528ab61596cb0a5d7e6ac35af7fa9e44a72c96a2e0b00ded01b8f44539e9cba8b0b359624ed3108fcd02bfd556f64e2dc9c2b40ff2c69d2692b13d35d0569cd903fe4f2028c7fe5b337ebbe3224ef1457d224c01eaf10f5a7714b698de214932bb9d47533166d9a63123a43936ebdfcdace299a571e5ed3672f0f463f73d47f6f32d3dba0a51e9d7b7b43e7387e1b16dff6f0b7ca49477b125c7bc208fc6a14af27d29c77463a049072397b1272bb728958756f5b6a08e310f1c707f05b70724d479c3831ac8e3991328f5fab851235494efab8039b053f1016109e127a6d38a682810177da50e21196be1d234832d289e61cb7a5922a576cf1551228f33ff95e2f31107752881432e2cbcff3a1406be46402fab4856827c6cef24b2c9cb8e341f404ac4d3ab5789e6578e7464c9f74261165ea482ec4c205fc4083d7c2a0377212b83492f8a56d61d4a4b37f61e41fa2d3464e922619dfc75cc950c93d5f087c47a48f1f2b9e70476f1ea0ee3e93ba26fd476cd5aa146e0d449947bb526bc96c9590970fcaa15eab10eee8d674319a85b0a658a89cca4e0821e70a2cf89a5446a7c020e31f959f817d3c251f15b0eb718d7cee8f6877f0570d6c2f4fd9b03e1333136691848aa25b4f1ce09bfe1bf8815fc11beca51a514c032def4a0733f82471ac33a158ee4b2d199c26a5b862105ebe19a263ea7854782ec2a681bbb1b2d10ebef74109a70da5bce0868960c9baaaa1da725b28ee4324e1ba169458a7cd7f693a0ed733a97577e7299ec16e779ff51d00775de1ca94f5d8701d3790fc836e0fcefd19ce20753faf0950a6c8f3832183532c908336d32d5dcd82a6a616b2bd99c384f07dd9cc144ef8f6494f6d6830a6046daf7a1c0fa0f0435509540ffeee926886608ba21352c51560ea7de1fda0813c2087e960d28bd7d0544678da5452f5dc19db4ca2017f427b61f13f064403a66fedc4adc0af9d3dde8725288219ceffebc7eae2eecc6bb8a1bbf3cf54a24913c1c92f132918c0bd75b05e5eb2f1061c67495709a9c424b81802edb1da2d75b8b511f1b446baecd0b973a0f03c16eb401704ca12e88b5ce5fe0e3f38c668cfa5858645071558a67c6b2e5dfe5dcfd414c7ac9b54ffc55eed4fe3eaca2214ca403c1dec02a86f8a5b20dcf37b3cf79f169e09f3b35be119e67947d6690b5fd9063b96ea7736543e0c6a4bc64729b567a128711e9ed019cdf8bcd08cd1b52ef5339edf1d13ecd9d6097cf8ed1a67ac5fbf12e2b03aac061f3ff75eb616a37d1ad45b69fc56f614f84cded1989b9ef865e6d2b50a4a477ba355d758ef4cb14d68524891287a2a2c80a9e05de5d57dadf346a7c92b4872be76cc57b1a086eb1074a5d183b0eee6446cf7eb6082c0113c3d0cc6b537374409bb8f14b712d35dc7c240753b467710bbba3fc8c6932cb92d3a854dab22a957242da86c764949de1e858d7063d97d8f5499af8d9f8cafc7814ef684e2b7ee959d08ea4a9dab2d4bf68ed53058bc66e7ca0032e79eb02ae84bb2b99ad2a7f050b564d87b551a7ee439050cb717856a691555c9a6db7002ee05255623ee1bdc6943c1884c260b3c6e9b7cd1c44c26e5dcbb7c99c45690087138bdb24947ce31290765d1efea60d39968e128f269cd3165c1a11e8b5af6a55f6fdc8ba2da6ad349091533635855a3fa3eb0e6d3de58018c0c97176cb8442eff6fcda9726b4370c3b06a3d7cf170b1b7b3dddc70d41028992a04845f3a232284cecc08b90f752b00880e7c899d479aed545eed20fd24219324ef119e16900fa8376b0f1ecaed0b34887cddf9606d1a0410053919b270931971d93b05b019ddb60c5dc1fadd7bd1092363807e90e463ad9cd292f3f5e1406ddc6ef213ebe5e353f5e70a2a4d75133642ba17035157f91695758069d5737bbc9b32509a96553fda1b622891a569d7a82dedbd75f45d59c31ef95d6a3f9aa30c85257ff74b33e3089c618c4ce7eb89c35be44e8f79a52153681fb1fbc1eac334d1a5b8c096f9f098e897572b85850bcb7589a0f7734a72d87c34d6f0a67172c6ac16240e0b46906b67b7b8a0267849220d501e8085f92ded1d999fdde20305fd58a0e2c5ae9c478ecb9c244c23971c9576c92e37ae4e91a51ad7f9722613e2100e52671900619429330a8c3cb0d4a6020af2161b3433ff667d9fc1c6dbf70d0087764088bb1bd0502584174c040f78f6ccb01ac73a49adfc25a7c187a41171adb007e159e3353a7c13a3eedf49257a7ad2f0d1fb87e3361d202f7b94f0e3f32a083992f6aab42e138e80c0029accbb4e96a0d857a3e257f5320606778cb97cc6421cf71fbc5c0dc687f707f527988dffebd07ee6013f0b44e0a70523ac65519782f274f88835c2cea8d1e9c20298a7467995adc96c0129b6e44d1e9755b8dc8b0fef4616a6ad20ccfbccda3b8804f1416e2f90861c0d89425d7cb31d814f4d2e1a014ab9095049c7829d1dd18161edf3e85e9db22200c8728856362c7558eebbf13a1d8ce97ed9f85cd7f99880b07d1593fb7b9556c20dd877de6219017a6207b62aac7934618bff087e26b26fc1a1c3012e8bf433d07ef9ca84884a60c5c90fc3857a50b9959786146bc830d2dcd3325369ac763806a90efe202de8adcf623d77b44848aa33f171e3ae3613e42c409501efd49c6c9b5eb76ea8d501a888835b8b45f3a02d83f656f2e528e139153b49cb548d1a843c7748bb3fcd72585a2cfb1aef84d3e5baebe3952c002625afc5e5b7f303257693f77fff37eac820ada860794b937d8ebe76720d1aad3565afac7f18ccf311306bdfe3683e76205403a45e27c713ee45432561af33fd24dad81d3e9c20b3442c0640848fc29b3b7532083984ac18f7eb185bb98a22c2a03c446f08be2d3308b66eff78d627480c3d26aed779e5a53d38e19d7a2c530603e60b67a86939accdbab1bb928e659be0b1d08afca91bab4521ffed3efa27fdf1cf691af95613ffe2d710b47b372622e157453bd7338abd2aa22c967f44c7f908d3366d961ddfa81226c3b95942bf9e6cf0aa373c887ee73d98bba4bea430d3f4d0e2a2bc1100265f4e99fa511f65d44cc2397b705eefb132ba219558546bc4e8427a9903cdb4d63d506caef9258312005a9186d80f7e44cfa1ee59fd7017c06283ac6005ab07250269a7ccf2258e1b3b3796a03a60fca355062a670e6c08c3a4ec3246db9d0524de196b14859163ed1c75c2feca994720fee4ec24729f6a3d3d13e2efb1d800c19a2b8d27f0b043c5cb6dac7fd91525634b715462757a2ded7b110c0fe44286c7bc0b54fa553c4e84d349d2d479042552ec91e4a2694ee3e1ddeb671679577f0bb37a24e29152314b7b2d6455a26e79be9200010ddc4b4a64ac94da0b326ab89a958054614c6bfbd8a2a4743ac67adba4cff89d98b485b34cd2680fb7fe056283edba8aa2832af0a53f620ada133b1b88b75faf56b310bfc1ed457236b9f8507377a87db57a3a1cda4f032a531206e725c5ad61522314f1a08537ccff253a000adb05093c9386b8a6ee7dbeb540e74e25d3c83a31a3ccad611e26aa300c429802bd4d1d7e75f23034d094813f8bab98c5e8a30f9936a3e34f8fbf6e8ec0b42fc59edd7c9447e5da6b7c7054692635a70d709dea086640dc7f341ec953974980eeada02a06737c1a26033fc6448596b3ceb473477e88dc67fb2e2cb803276eb584cb4e210a769a0b19bc4edf340a826dc98f55d734e147e472b265b9291087361bd90ddfd458e246057bd190d73dea2b92b9cb78864dea05d70fca389a2ba3a9bc07802000e2d5d4b6d62aaa3b64e034ac205d0f33e0731f19554b746ff25a817151bda7b8d414c6b001fef2db56ad27a392e50712e37a32305e3308148adb64b42ffac9fbb99ccfcc831598a5806005f9c0ed585edf98d22ded4f4e89a9262b1400b1488e8b736a66684761803b771f984b296e1f3acf57eabed7fc6d84781c0fe18aa9fcea41622733c12fa0228b5014a83decc46a0a2a4aa4c5b3857fedfbcfc7d8fbe16c044edd377ffd10c43090b30a13aaea9550cdb4abef5657b6c8e4fc7f0da79cbb408cad2c45fa70769664e67c7c5e09144a06ccb9c6b877a35f1f965460b6152187c0c5fd97c66d080e2a34b433f15c8ed0c3a1c956dc841453ebc5ac2940c3b811b7456e6b9287032b756bdce6705f29832090532c9241929f902c1134abf557054bae7dce513afd0b01642e459829944c141adab62d4773363e1cfbb25727d57ab75b0b35f6bbf2e82e582d8fd84163a256662570df3510f3fb68fa5f700bffd48797088682df150837b9c5d58dde98ac3a6c02a29a870293511fa8a5ce12ce66438cb4a7a270b6469c34ad7e965f4620b52a8c5856afe888bf6d46405ba13ae29087057745a92591786e2552fde4f4946da7ab74ce917054b59d67a301d59132a4751b775ef244f24b4ae5890f9135ad32f89af787b785d66d2f830633d7eb10740ce8e66603616665d962add770595e63b8cc9d2ea8d47f42c9940404b8bf44d308b9234af4b4a97b07c8713a7a249531bc416368153639ed2378d12fb05ad4912602f14d69f0d4dda48340abcf5c7eeb8976df7d83c397299ec3b957f177eabef6efe85d379bc6bf8d89f43e80ec8313e222873e7a57ce5eb1a1aca56a11c7b5d844279616964b8917c8cc1e67a57b27d7881468ff6b664ad0c474f0a7d5cdd9abb6150f1cdc7372ad94e0d42e98d5e9fb842669653e6b048de0a354e6b8c4d0400722714a75216891dc2476981dd3e8346c9c8f2a2c0b4ad3ce17fe33e089b6b171f69251a098c83ac53e34d13fb4e9b6e1990b1ff047ea5209581b1cd01a4da6c9a3e4d79c8ef61135fa95e64c7ab21750fa62a446a137abd02897b2e0448f5f451560acf151b0f9f109f3c5e83a4868bb9bfa8fd2425541c070d11c76aa059053137c78df21d7625bc0528d9c8df82b0bccfe8348d5130308c5f1d305ac40268e593d1a3a25800a5eee9d8f4cd218eef5b3ea21307928920876384008ab7762ce4554f81eadecb036f44a39e43179e2bb690f47e60ac5ab4748b4faf2931fefb609d95f0059a5d0ed40b972216c3f0e90c3877b5df8b7735b8e2c1a30e1b68f96fb421e87234be8fa82f65e759b3fcf41b10839dd39b69dceb3e81304ef819344f74d5fc10d2c0d15c4af00711cfa7e9ed5de9226d49121cf6a331e067a5201a068e06bdbb725a22c29ec0cc0ecc3f223cffacd5b076ad215ddf0be3596a025fc167cb0c9975edcbb5c2e361c91b2ddc00a2a024e8f43377b2ae47d046276d68d97283f3edb1bdcd441a3ad6eada9fafcd154d5b862acdecef08b998fcd817590aa4a26371637e72c59789f0ce2c54b3a332d51205c8ab3372364a82258c2327212c2e15c73a2893f30b4823ee9f5f735b6d4deb111ecacde3069e29ab1b7627555d463fea3124a378003bcfd58b35faa0aec4f18d866bff3fe82c23cc271479c46b214bdb669218111e21ca521d0856ddd7a4ee6b07223edc1090eb6120a04ddc454c160bf5e6c5d7c52771d32b47cb720f706294ef0b8e4a1321fdf34d8742c7a5bea32f4b314ed16b75d27590b92cee8ffeb6da943337435831daabd19bf854bc0b69b4b8f6f285d2cbfdf73663474c1bc8969f74dc26329df098dfa1cdd9495e950b0af52526eb24bc7c698e51359490a7117a64c7805e02b7ca86477d26d6cf48d33fae90f33fb6fa9480d824fd9de2782aa8bc3cb7d1f9fd08d4be1299078ed5379053140b1f1a64376ae59924d1f4c0c27064504b234758b050bc64c9b03915172da978caf9c2d28fd791233a5b4692564d38cdf7a1dd4f4a7871357515b30af840e7e118faf894d7734201ec1955dfe9c8f990fc5f6e2124d6b8ab31dd42eebfd7e53bc9939458fa6740721579f2e2421463f932edce0e38c661ad91f499faf0c4b3548b533744a9383456f528e74942520881839283b6626a329bb938463410fb52a202e2a941d72e9830e45fe1fbc866c98ebb74041ac7350b24f1bc02b8c3a2d3e8d75b605cb824b17eb99b207ba5f41cbe1d5426f035dce10d5aedb74f7d1abafe43dc95cfc0f13b1bf1fd727d06e21f7213c723978b6afb4ced0962cf9f516dbb510e61a2f3142ee36018c0f2ff10b05e8332d9e47e4d91ab6e9af2c2e5044b60662f2ebb6db46cd921ebb8cfc81e52a4a49ded5944730db6043209a757d2b73f90bb7045e580085335c8def3395f7ff768144b9a443c513a1fa89b107b281d0091d8ca82f6d509362eae680cd5c76637574542da7ac3cc19ab5c9c8ef26b9310dc7d970fedbe5bc6c6eb6925c557f71f941229cc1bdf5e461e26e2161c4f12eeed46e75c4672a18ad88bb46e7097c5fbb9d95ce695a2710f66e714d1c7ca4b30b691f8e12fb5be9b21243dea557c8a99154480f0d368a64e74e1c09558864a29244e07afa68e9d26f956b820817c9d34f380e51c5e67db9f8498cb15d6a28513a3dcdb1f12d5fb61a9ae817d3945a328a8ff422729f2bbea04e63dfacda4cff475b56867ce70a4cc8a57f34e147f91cca33b14093299e85e087f49fcdd18e0630606f360d4c0b687cc93c34be8c5ac2308b5cbbc50dcaea3487bdf7fe8980d17a901ffceac60e0cb2a4ec855c27c3597f15f18c790bf84016aaa341c69cdeb1138d0d3e94f8a2e8241203aa6ba6c1a01fe5955a6d31c7ace520ae60caf3ccd9dc7e3a1167bdd7ef2c19e028dd3ff62dca2fcc27429d39ccdce1712b1709ff5eb9bc0cec45aacc59c1c1bd1a0ac000fd89485d89d481c6d7f31a32978a1c23a7d075d966615767a9b568821c1454ef67a9b256dc81b5fd3347e579be8abccf08e629e507999ca8132b32630c49894ba5cc97b4465ce5b2d186aefb3446c4e22376afa550ef1887bdcd8715ad9566a0b9d0a0bb4ed2afdd70ceb1947cb86183434e490e7857f6704a82d76d3f6462e9b718f3d0a547db076e6047dc226bfd233e4e97691001fb5d9d853f34106c7925e3e978a46e36d892feaf3837bbc53b19bb86d84fcfee2177445dad6b24956de5ca03f8f124fed6c0b93fff056a32e18743333bb1b802dbdf52ed9ca0fc3105016683ac7067d8b171a1ec7182e3b99c73b75a4958cedac1bd1bbec42e86e573e9a7487aaa005a1857e94aa25fd89520070a4db9567bada9dd624510aa6d80f82a1ac4fc59a1cd8719b69ad0f01ffd86c367ba96d92e4b8f79f36c378ce98c9a7e2f6df6220cc42276484d84f4e9f1d9d49a69a3aec4fe1cc46a18fcf34401eb638b0f4630cb87e3fb9c4c26355fbe9f8a2405cb2e88882cb5ed439b9ea3ecd759cdd2352925f0d7faf78c0389c3204f7248a3b12163be6632201ed634c9a0d9821946496a69580193febaa052c0b0b3039b5345059b9e6ca2a81b5476b784fba652bb0fe32b08eea42cb77d46ef4f7c2ecb076d70a05ae45935224050f2a9819e62ca923257fc0128501f1863556d9d41b869902fbb2c272b67098703d361731bf6e742c783b186568d8ff47c2bc536b6d751375d270acce4fc99477ba9be8ad1485a08d2fc633886bdb6aafef5874e60aa1e15ee8aa98d2e99810d3e1462b162480ab7be3cde2cd1853d8ba35637f0879e6972c6c617ac0b45503f59c76287b647798c88f73a9c2e1200ac2c6c2ae2f5f1f631bbee5eaeec734b95257e07e4d74dd9eed8c373d09bbdbd02c35e5c62c9736fef5230b15db85e3b4737f9e93cafbc37882d2fd5681d10a79d847a3ded4bc7cb350484435474dd3a1bebbfa0fdbee6e1b601a551297bc960d21f91a64e6da4962e77a904675443c26c40888e2e0ee40e99011c932c2691cab46cdae2e44fb480dfbea3e0f09e360619ae9a43ee8ecfd632d540535054493f9d3d887dc93c7ed9f247a93ec90eb3ad96e871c2bdcc95357442b34b9b20b8015a6daa6ff3f2bc6d813bc800caddd9232e4aa3e2abcc767a9c5fa7a0caf300b2a97daa3ba37893fc5f013a2f9b9284e9ad521185fecbb88f9f3bc7db90f9f84f94a44a2842c028dd1856e055483e8f7547561ba786ba5a1fc09494f62474f9275de61c862df7f784d3195eb85e15ee77bda32cff67b2e097f4ee4ea1df7280593e32df0614b1bc194dd0228d37ef607d7b87b10b0511b768c463e330632d5190ff6803ff67a7669160544d59f72bc7aac31365e53046e11008562fc5537ba7d97c9840cc0aa2b4416278ea58f6134da87d458616703ac50b6199853e6acd47c0fb8107fab914377afebff7d0a3e74ee1c8f062d8d478f9332fe46c38c0ec7338bc9a468a278fb125b2ed9687aacefa50b100d1746d1cdeacad97aa79a81a81e71681fca2ebb46d947d9235b09d14714fc72478e3e8f51b8b771bff176362612d160955fd4aa9886616ccb0b352e147c24ce30c5f3159e7fe1bc2de8c5758bcd231006ffe244e51e2a4896e1ea7781eff6d0cdf8f3bf2ed6cd3a6bbf82ecbbd9a4ed3213b68219020a733edba016520c54c5edbe028fb1f5c7fac6f691cce2b3426a99db6bfa1ed968508af384088ccb044d201c5e0036d52469ff345076f70be50712629f045762220c391bff2f1abd86fee40145fc085082c79b79add4fcf195e46c40c5a0bdeed93c035b570b2750e465569678d210c05072b74e8489da502b0009fb39ea9ef7642176d9e81628ae0c8dfec0dba7f12d8861f2a0cfb9d48be4aacef63e2a30d76b0cdd74257c632ead4f769b00b284f9bcdfd855b0e31ba65aa0e1e354071f92728a7f7ccbb24ec0c88a1db65aaf5975c13909e023576576af0b5a9208bf2dff53a3fab520296338e7639c1f9312ed64808d99e3d83e87a8b7436996353e72eab3d1a7c2b46c3a6aa3ad7daa67b74c4375e649f8b7fca65b6021b3931198bdf8ad17f65f961ab61eed70fe03dbd27a685f79e7c7e8153a025f6b8480aa7bc3155d3cf818348e9337b3ad93b6bbd46b272fa51399cbb636da6cebce6943982220cfd8ab48835ca7a04ba8d5333945a71ca444c29e98b463884c32711cd3c7ef015a758a585756ad8b82274c555440afc1364f1f330ab13df0d6785c407db7335013bce1c9be122e620967efe4f9b5c7a0d706d2c317c9284ee606d558e675487609187f56117ba0fc0b3ef21fcfd9691897bd241bc45b170de5d24a281cf8c27fd3d05837b283494769bb8a0ef3eea2a1228618e4e61b83555ab10b65372bf53e684c36f41f4e584813109640ca0c6d2ada0d3dcf28167a8b79510de95e8451c43a9fa1912fd815e7c501b6398e3cb8a301f068dfe2b0256e718b680f08396855c276f79608a7c7e0bc4d39614831a6eb9b31b353b3317b8d8335cccd965a75f9308834e312debc574e8cef8844c0d25b52a90aa210ff9a9d6f2f76da43db2c2d5fd41943b95366d857d6e609ec1a9656aefc1cf454188ad07e1747563ad349378ddf6eb90911afc55c4068feba982ab1e0d37f01bffe393bed4b75022b8fdc233c1769487f8d4ee6c32d1cd90c74fdb97f971aa8498baa4fc380187e761267f55aca5fc37091c23d694ab6550efac54b3e7617c0fe622d55668fa7a9c50f8bf4feb13b4748b2eeb2b272b4597c7000c57f6309f4d2ac89a235ce147b90872b11c8c838a393b8f11653b01d91ef16c21f19bf5f93d55acc8611df9c77ed6695a58c2c89bd7b59dc6e4f594d9062274da2d55594f6792da2b7cd73eafda1171b075c1daf9a18dfe4c85490536e53334776273cb4a81c7106f194d8cb772586ed38418426a711d50303dc40ec96b7ac09f8f8791878c4a6230350b159ff6f78caaa8f1121a5d6a53c0d96b0900c12dad6e012ebdd7f5f645c5c004acdc2f067d22cae8712ff9fec3926d4db61a8098a9c991069dc6ee0d7c7d5a1551d27c3821f3e2ab3ba304812617d686fd4961ccdb24b3afbc228fc706e2438cd8efa231b016d7173fbcf83c2ab689d405501dcd7120a1dbc843510544c86e88c5f901393dd1f7454a19f77a97fac966450c9cc2697d11e7231f280844f32e08840ae7274a2e116005ecd8129271546e7edc8f8d2cf2a4c91451a04e7f00c417c7364daec6813f173a6e9b022df1ef3606560be078b9227e47f2a2594c057f2ad0873d55f287d2bd67f51aace37be127fcd7e7b4e8798f453db393777a1527a155951d8a1595ccaff5a07d0d509095c830df48455f7738f5cf5f2f7fc99ade251a8eb0697d562514f2de4273f430c495d4ab0b8fbcb990978e3c0b7fa1e16ca20a75179e2c02637235627c9841d12af5c6fd90478f2f25a02e8d77dfe5695411edc8960d2a62ed75d67dea18d05b9bca05dca1d37f9a118e29c3210ff86ecc259c8dc26060592437f31596fc8b849e1dd24ac6f03dc552136678240fb61ba94fdd62fab8289e23d628b857797a9c6cc33765bee40c5e005cce0c81f86de0c0e06ec84e114f4ec728603f70b70597c88c6618b18bbb10a552478498d563a15088fcb5e83524979e343e04cba91907c512ff6f2c4811fcc86225b9cffb9b8377965d30d9c5797add3324c99d2f08b7225a4c5f7debbf8191b3405dd6edef380256645534cfce4d732017bfc2d7e725845c4ebaab90e63f873541f37ec4feb1c96fd6f4cac0fcd5356441bdf77b43d70a6126f293d349403b5b1d987221576de343bef7385c6f1430b878ac569ca50aa179020013e9e588b1650350a61666e4421b588292c3a52f26dd34093b3416fc17a186893214d55c7e3a77a45ae362cba7e0edfe7e31cb408b6da8cac1658f46c41064bd91bb9e494aa70b754f2ceddb34f9a659b41d6589debc49e6a34c013fdf666f6bf30a37ec33588ab15a6446f477e68d137da94b76ceee0ee19ce09574c49dd9d137616273f6ff2136639136cea7bde5d55402f6ec2b6a328f04cc47cc90de69bd839c456e3a0f52ad4b9925c68a8060925e263a2d236ddf6790a1246cb2c2532962d8b1c9f3d943b67b0a1d49afcd59b9458c97948d3cc27e6101c005de31eee7677105f3c594e340c958b72c52336f6a484d77e5310404823a4a834a5c89a9d82372d022488b75433b5c28e5e8a33d35c569b80b961287050ddcb886debaaa7faab1d1dd630eaae626b6e8ed69fef9eddbb41139e728ec9ae0864b898f04e43fabd45806feff02f6a520750c9a7fa45d8a38918457d95c8560981ec81b70c1ed379bbd4a24e3a17ad42d3b16073a25d99e5cf42bdf8f93b090819bb9c1c62849f352984c96fb958f072d0fa241b909e254021807b95cc04a838bf5fe835308b7af9b84b390b7c15e7fb702932ac3db1cbc4eba8462ae26f11fa7c66316929964c7bb19bf657f0f593db1242196043d246c33d7f576bb009e29d95323fbc4cfa89657b407e04778db8261277ba778f7625198b68f1305aacd3602ecbbfcc6fcfa771d297a685d351e96256a25096bd9673e017ede1e1f7089d9fc7a16b0fd0ed37955b07ed3dea0a8169e3489a77f2b350874e05cd113a62e7d671783845814bb8f19458a93b7b6c314c79e667653ac5961838ab4393eaeaf3a76cd3a80b01973b0e1a73dcbd4a4e265ccbbe6a7014a1ded9b7096741867a5bef2ab6474a19dd10f39fdab89e147fc3e3ed0a03129338c7fd91a2741f8e0896f3e17adc0279bfca3b622aebf55e72f8c941b5aeea48baee197ccad019a10be467354f3274b48ccc9dd54f91f5c4bae5555ddb96665d55900f01f16c6b4e2c2ed202eabb96341fdfe12615fe695471787c448822ff8d7ca3ac2a4014f38328697fe6b7db450377ac6aacdb14468d30ee261840b872c3daf036afb5501d03874a20f5995f55531c9193465c9b4d35a2d39595f080cd7b2c555229b1aae1de1b765990b915f502048e4a3c7a942e4f72ee53a609aa8bcdbe60f706f752b376d54d80993a62fe146374f76355c7e0cebcfcc56cd2efff07c83450c39fa61557fa8124bdaf5639f0121ea042109628aef4eb2d927a54428d83f18c55fa952cce8bebd4daa44fe01b0ec5176ca3804e9ca906a667d04bf8def022a66bebe1fb6c1951cfa922b6ed6326a7ae0e0cb0b2237e0bca57cec3ff57a1b71043034d925e7aa196445f3ac68600afa761efbade5f5341753ea7b8762a12b827fc5d2e88f9b71c67af02467c1a8dd871fde34bab52855053b7863deb006fd7a7477d66004a658fbb1d1045ad992e79f5abb642f9e6b8fdbe36457e2e4012134cb2b95ba2d2515010215d1dc973358251d22fe3dcc60458aaa4cab4e67aebea94210cd38574c79875ba2a6c3347edfcf7009624cfea0f49cb02a140b54fbc0c8f47eb92cfa646aa2520a7274da30239613392994d33bd5fe0842deecec953fa7a3379975855bc4edc2b5b593c5aab75456d2572600bd0671635c3d609d9f8831177d85f7a9595bf78bfcda29a82833c498e784ad1998549dd18adc21876e4a2b31630e8650ffbcbc2439c6668dc6e6881e38aade77fc533f27f7c2322b590e8b2eb3dacb291376fea12d54399df00ebaf187ffce41aae11812c90f210c01e363f30c4fd68cbc87b67980004c7b2df7208f20ac07cd78850e266a079043b65c578665c8646bc8395a2e1a92c2b04360cb4ac8eff71a2bccb711180c65af7f303492cd550b5165b53fa349d33381a61105e3b09cf813516baf31eddaa30170bca43f8b491b427b832110a2d5de4fa3277f4ed9e606556c2e3cee3fad3c2624b55743cbf71e143070ca50390322929a804e1a054f885ff2e2de8d06b709c9fdc6eadc5fc2b59619a16c8c5a2eba11817b491af6e94344353c361a48ee7ac4b060993c679c6ed1792c6a21067d61692abdc8e04155829992c8d16f807b1d7c3eec3479ec006616a813c2208764257021e2573fa3bffca3e198ea768e871fe846b1430a44d9ac6641e75e2196aae26b508108e1830152e8ca84395116f6c25e85d41ecd726573a89c5bcca8a69eb0b3386103d63e80227c3f77425251907518a0679aa5501300d57ca6e5286ab2b3c213b10f71c8292934b5c0aec29c857d0ad8822d3332aa6ed9b636cb6991978f57aa779e4520aeefea23af578389a643e0694a8cc024b3c1602512d35eda5a13bbcfa581250e9265bed4cd09980a848c76a0c9cb16877fe300adcb9d2e0bb772320d0eba00ec5aed37cda3cbfab12a314a2c49c75342bbfc87140185d7d243961050ab54899f51c76a54a59b9b56abd2f763e08cf70a6ec29fcb618730825b677a0204166ab1c80f26fe33634779e05e5dddb20928d7e2cb9e87e3e0c54419782622748ade346affedd0d52905a7ad22caf2a1dc05e5392e17d9add314c4f80cc3b3bc50f6e38e0e9f89bbc8fb4b80f7965675a4f70d70dc29c131f99759dca4e61916b77b2cddd0b90cd37dee7070d9a908a67cd9c6d1463d8dcc0b15a9ad9a540709804e6385735dc7b30ff6bdc46662ad1076b66c990956e0a91e9257fcd36973e090bdb65a367a6db638abd89153b03dea3e16a5f489e316885c57c60fdebdbb9064f99cc18ea80d81f314267080a94c708421fbec41eb9317816f31f4794dbde9d13074753f036f9ee74aeade0c0188c227a0c497bf3ed6405104ca257ac0dd2f576163bb47e0afe4f89a4850868198c292f76a2f69ed81ade058a6f9bcfc0a74448deb89fd6537d9d97f1b6b08bf875cc2a9bb25b78576b8958857fe39351c814bae322ac8dbdc433a8b6d209d0c8676510469f64589f5e4175ae6bd7fabb2967ba1233de14965fedfb9af8d0e51fa2d2fe3e2e147d45d23796e546ad901554968699b42466fcca1c4ae5553364bd74458b0be786466b63add36d8c23962ecbe8cd6bde8d78b001dbbfe9d9e85a37a0a168533eb2fff4bee342fa9c90e3f918279ca483a44d52fc752ff2646d219d137c69468d6e90d1ea9732061715f8385b5e49adfc1a896fbc9d336bef8301def01dd5c611d95a97d2e6677a9c36d86a8ed24faca902144bc0d8bf9f00cb0506070c80561195ada13a31c9f72b1413fa88b98ea8659585ead3f72a4535e0262f08431f4d6f579ca053edc752d5521356c9e1c8e922aff5c6822c2bbc3a16bed2a552dbf79d8fbbb72c891a11cef84415d53c9b9a77a216dd633752be223e397540f3054be458ef9794ff90b0b20ba92582605145e96484b24f76a61c751af29e8cf256fa0d79f8935134fff156b3eaae102eb616aab6528493fa6d61cef1f00a191b8123db35c87832a146af30b252e44e1b8041e88c8a169ddf3706d5b1176d70a47bd0a3f440e6fcb6c2faffa847067e71e7f8f02a962d3916c69ad56374657d953372a315ecf976cc6c1a2fcb25fd747991b55fa9aa610eb3ad77b78015b9203c9d43368551ea155bb64fd93df99dbd1840c1ade7fc466e26bfc0bee16a95224a3aa4d88df2764072aa7f14b456b3b1ad8819ea823d80ad979b8a6cbb6bebd73462b5b95177216df29f9eb9a8673ff1951a881a9ae5e2859c184c567b7f6ecc199fb4c5cfb309fd2a81237959d418c7024f500d95b6dfb56f1d5283d091ac4003e287d8254c4563ecdd79ccea3bb358145a8601b19021eda3644dd818caa088aa643cfbfb5fba9d3af59503e7ee8169425ce29fe41449e84e5fe8053e4ab9c63b455c88ef6cb65c0c4c6edc7060e464b5f06607095ecd541e99e767e8730277251b0fc84d348cc5dfdc1ccbd19f18c6c4847208ee9ec0e7fd252f48acdc823bc95d4cfba270dcc2d42a35d6b56ae6f3eb074eb9d47f6d50726080e2775b4d25c8e282b7fb284a6f188f446b108b67f3ae6c5ec5208933db703fabd1fcc6974ee50fb411b03459751f2259d5d99871c6532128cfa0b8d819977fed67bb2acb0770a9628ba1e6da77aa9d03c9842be5518c0dad38267c14bb279df32317f7d3d5e1b4f4dc667052c852ad751bb5c674394476ffd5342cf39c1870266eea485d364d3ba1b9a83b1a8e514a3c76a8b80bba4c8393e41b734e8b4ddc4b839c3f651e6408911222543da2b0ed57a00838cd264d1d13a9e2058c2490f50a01c6e956222ab8bc8caadcfd1b22f31d0bb5cbbbb704213ce435cff8fba6b5db5744002cd0b15de3bedbe65ca296aec798e39c974ec335654b08b41f065fd26dde0181d524d79c2af6802ecd025940305c0bc05af5c566a517646cbe9a6d8b1584b55837b4cadcbd398467fba8e2d6a13d35bea8f6b747e87dc28b609d081dc2e7a72df1cb7228ecdaf8dca4ae697e6dda5e63dd42da9bf60a39dbcd40e8217abbd49cf9d4b402da28e7e610e692f2b104a947050401ea670d826e5dd4c7c98662c6176bb51e84309ae4696922833d07dc31a2beae6fde8e1d16e7fba63776a62c8f919cbf0b31cc721c437ac95be7afae1605f4c577857e3d41ff04dc99ee8211481306e3eb258aba44b2df8d08874f497d8fc417b7ef34b7df750dbeabdb2e0f4e9518a03829c8cc35067df52d9f86368b52f5951b1fae6318db4dfaa309c51a4efc68717037ae5ce9655bbafb3e42061f56e54ad269f21f02588e5dc31ad201c71aee81a789f4c4775c4ffcc0144247431dbd96985193fde88c0ce6620d6c18848111ec4b4e4e6a62366b0cd0c4b0bf002a0957695d2ce9aa0569f9cdb6b0ab464553596463f119a64f460750ce9d4b9601aa1fd0fa7742f3eaebea4b05dab005466767e43022b34d1ee2abdc2388ca525745f2547c493c84b0f66644df6daf458b12b2d1cba88c6496cc033a8c5275b9c8cee78794632539c4db12ec1d30a0bd29b6657d31023067933dea5d186d695b2d248bfcf41bbc734ac873009a02d9d71fd631706d73d6d134f2895ba7691f49a74ed5a8f1721df29d1d48d5d059d579f020ccd6584896289d96dfe4ca69807d1f5293be9a9a53d5f3dc01b8d98beb21940ac6d4c65a8716b4fcc36bc1ea715462f09445ea4e8593423f7b55f0d83cef0273342064d44e031cedc597171441e2c07288fe4d60c7863cea1df2f1b602d51fef35f6d9559f72d910db170eb6ff49256174b14b127584edf663242a14b6f3ddecfab887e6b0d6b464f7e26d9e83cb7846794044855a1869d5271db05b021a2b1ca82e13a19dc02d8a711dc2d0d472c592969093edf3607f8983d6132415544b3b1d54b1f13c90f4234c2692733a44e65210110230363c8fe5c13e334d61eb58d30efdc1f07e99c637bef20e7b2de59c84beea3993b3ea01036c15555c99e6f8ef18741bf258d4b77e201743cc1da0d87d7cc8fe5cde5fb3bd41237e160598f57be803015682c28b117440fd38d46b4a34f888fd6b489c805edd989046d0724b903a886bcad4dd3d56c99244eaaa4a4bf292e6da8aeafea73edb131843791decd6751f6cb80e703b69d01b28aff2287bd8528cf9167efbcc3da5e7538316bdb46ece2a384ece5a794085779384cbd829642300245cb506affad5d9986e966964c58472692aba614a5292cbf3edca1dab17405ab11b92f76d78bcfbc0d8d7583d30cd519e7858a92f254178a5f7f61053ba6e9f9906a636760a4b40494f34f85d9cb74edcb1eefc3950d99489b2522545e00b9d524280a84a9c3818f85a2258c369461152f9d0fa04984c3386ced7c9b9302786307b2ceced660412908b81551798ba74d427377fe4e57eb2e3048ed70cdc240e592fbfc4a753b67813b25a2e9c855b9652c19307a66dfd50c9448d11e5eea04606ccf2ee4ceed450c8564c047ac0ccbad74a1a0a04f0de69f72a75cc98ae5ae074f708f0437e97ea6770880e125526f904ec18fdd20d46ea346b158c002ad0f2cf372c12c51bd35ff4dc49f693bdd25c6d458ef3f0b337e7719a88c48d1df05bce87634216350b1a1c73fe9f55200b11fef5d24969388e8e959fecda9a60b36399828026014555f209cbf4dfd39adf2362b3d25963b9d6597b97e5e04bd01f0e4ffa32876f4985a58cd116a0a426e79463170933c8a14666cf9093b0b9fa18c11ea004ab1552c68488f5d4595f84f540e30f6f3933d642fd022118202ff1bdedd4c1df57d98d4aacc79d1cd44cd89a569112e968574e1e839e2bd0a4da4f7bec0aeb30c5823b731aa2221d149037e6c93acc06d3b5406fd2a9741bfc2ed29c3b553caf3bc7359f5b53d2d9bb254500ad4d5e9aabf1e373eb8fd43bf802b4f6048e48c7fc0474b12b56a1297d9888a325c9993ea33c10c3ca9604367236122feb8426246fbbf5b81ba5935de9d561038848cee01b4d626b80f98fc505109ff4dce46ccc26c479a88dc85dcdae4f86e6b452b493b6c6d395e0aa93e75f2cb645fed8cd7b4a04d4fe64a0b8c61921c4556fab8d27f43e1ceab066f4b455f5b384d4e23383986527aa4a7b083e8c633c30ab381ad61548401caf29006f024a968921723e4425e0b343e1611c64c316ef9a4a6ec9b9feed09d86d419913729a02908b6a5ff271fee3929b931cbb184945566619f11ca2006dffe4029ed968eeb019b645a111a617f421cabe7fa43f78ce3584bc763f326c3ffae2355a60ae16e62190c92dd5709673690bbe4cf3f222c9617468995bc3f2d99116c9f91168b8eb787fb56adf2d26bde121daa47338889053f23e4c1f0cbc0435db25d31034bf1d9ad1d45ea54805918f296e44ae77a7176a74ec6d6a5e1edfdcfcf6d54685ca893da9bb54bc1f2ceb43f9db086a74e4e8e1ef384b445c724365858a3be07e0218fa4cb227088455712fbeb8f6f8aa9d490a978836c529fb7108601bc3e99a9262d59b6df6f3770a3ee1d91d689ad71349599b739bf39d4cf35dfd140f842e936716e98eb367b83386ee968d4702138aebee5cd285ee3242df5cb8eaae2ef5b78e438bd307606635a36780689d8069e31f9befea76afb99420827f7884692ada05fdae23372f65db1529926b3eeb9d1af99c39bcb49da40dcd1275816b92bbe5f41a828eedcb4abbb93bdd454b2b7d131169156d3afd1d480dfa50e7a46b0baf296007d966e35ae0c413fa272a8b903efd21e217a880f160cfb3f2a252dedd53f87f8cc189aec0c117e53d813c5d940a760a9cfd5310d7ae3122a3eb76b461ee0563c3a850a6246be215e513c2643fa4c3e9ccf4b537456f778841752caf372beb717447f6fc90fd9323fa9845225cc8100a665eab3f952eb6d0638c2ac907de21defede537d0611046a4ef88cece976c0c96a5f1b5460ce8aee33b4c01700f7fa61c74e397a1760e8a225f118d3956035091fb32f77dd7260dded2137bef6807bc00de415f74147c9b31217d4c3a08e2e81a4ed0eab65e79ce36452dfdbb618f9c9bc344e9fb5c9ec6c961870db4b28ea97e38cb6611f4830141bfa36f98c23560ce8d7e336358d6e23a6125c36a8587bcd9631740722eaa03420e7008e84659bd46f513e5aad19ab4630636d1009df6a988aeec3737d90cdef6d8f756532d0c6f5143f3baf3b837efc01ef26b00206ee11e1264d43e666d0cbe7169e799c930e2347ff1b965483a2930f3a39c24a48917541e8da7ea06e9429a016f571a209a893189f7578fc1aa0a38530c58b7a6bd50e0a70e2c9268af4157b3d7e2894bd32688007175c304196ad54baac6dbff68d9117e47cb2c6986ac8803653706a91ce9f9a78a1369598d3b79e47e01c5bb94bf35957866197d6144b257ae2254c1d0ef0ea2503b714d3bca6b4db7c5c75c1deb473d122a7c7dff9fb2800d57cce2fc387c11dcbca3214f3522ae338ae5c15c22202dd5de80de8f7461a6ba22817aa1fe3f76e1a89c3b20c240c29f87d8512b735ac2410448d3b9f2322965967b1fec2f4a76c79f4640ea119ff47bfbe6fe6a8e336274370d672a4165c9bee25fee56a2649839bd33d9febef1c54cd0a38cf12282c88ba60c1dc2ad618078659e3884640ca1ea78c59d21a62525e43d9904920ec7dfb58b2a40a6e36e8be79fae2b162e4e6e07da5727b45e5eccfb99bb6af28383fd33fe5e8095a7e4aed7fa4dbfd9ad36ce29d9d724cd90d479d9d483acb3d6265f28c931e2bbcc3f6d195176e88a17ee5087ad22f03c530bbda9f3926386a14a2b61bfcc4430268cf4c940709a0077f8cc491a35ab292d3aef6a3c8fbb8a41c67efcf7ae6e3e5734c94fb4635d0456c3810bbd1763b66c8a9e330fc63dc7b284a91c37ce1dc46320a6d5914a5b85225bb9a3fcb30d9b91efaffd7cc7b993a243e219aa04b59998aee97ba6b2235664104aae6645d9a0d7c503f633807de9b31ad8e7b74cba5d0e03e11f90b6e785085d35041866f8f32f0e0247dd23a9c1f7e2996a85aa02597963fe5c622b7e404cba09894ef8b56d43dde66f9db911c5f9271751b333629b0d90bc2776ee9a37408f9832c65cdbe0a3c95e97b4d3a1a0521a1c6d420d26f324e52b85326a1010588daa8cda2e7fdbaa57ee264ef322622dd3a19050d03f0e6dc2c7d870b83f807ee1f34ea63c26744554602243fff9861e9e34c4c7fde429b067777d40fe94002946b4acb919b901007bfd0ddb7d641e96218c697026b6cdf1317cb4001fb360a891f995155c67bd7b8ff004d459f9bb4c4b49ee646b89423814b2f36391b47a3c0b433c20e555fdfd229c196eb9c83d19171e009dfa7b189da2a69522f548ec0f581290cf3f9179536c6d2f2ab93950e83f7684c055d43fd195e2755e4e6d1c5ab0b69a99cfa40af1846d01db23129080c226da982b724414339633c72b26f8e2a6971e2017b266d57118566f92a25c7f4a29c5f1c50609072cfb53584e94b68fe72816c647def9c2b60f5ec1c245339fcc9f871c9ebb7899c5ab052d450208c008517ab884ea41d9a28abe2c78c9a352248ddb437dda14025115890197eb4a87becd2f033f4a47c4d7e52c69be01c22d6325b4e3893df922da0ffc25564f2ad10b5f37346b61ecd1e57c80fd91d8a6943a54c3c0bf5aa23a8ed6165836e3fe3b57885a75a9e525c75fafac9bab18a40ae793de049ac83e4621fbd294c6c40c849e926b5e5a009610159e1983c62e2944e8c2bcb166237790f839e5c26ac4df095ba5ce663f0b80cc7ea5e8614513f49ee54c3a954fdc2cedacd4ecd5fe250d94a4bae0d1d7243a63a6279ff1b2ba909838351a4ead8fb3a6e088f57468e7a1b96fae458613e5a98fd6dc40ddbfa2f562e48ba9912b89e6d1cbdafb9ab4477ebcd78f4c2ac446530d50ae9c80c68710a3a85967deae4f726dde9f1d839ef9bc7af81891c84c322b97d79bdf29eec9a5e154800f759941d1e3ff140ca3038faaf1ccd489de57f089c3b3b3893df4584d183f5b19bb090002881bfd9e625b751d0dcb586fbd2f555750b9c4f8825b7e105ef28ea9868eea8e4f326ed86ea070be023ddfc3212fc03797c42fc8c5ebc150b0471da072c79444ce6e97c484a50ddc2fe9ac4fb65fe8d5c49221ab91be17a2b613120d37eb9569092712389028a1165d91ce7c02a76e0313833771271ca80d61291a70e93422437c5405cd5e3a4f8a6cd2a0747aa9be48225f09d511a88ddd9a4bf7b6a2142dacae96da121f20550b4d46669538739eaddc451e343ea50ec37b423881ae6cefd2a4576332f7639ca93c365c4634c7ec464cbc51da3540a8e7ebaa2b83589819a9fbe1e4a4bf03298f36d5f4282dae66374a644d2b0883d01375620f1c5b3dab7114c17e9b23ab92fa9b0c200603d2f3030a2216673c60f216171d340464efcb7b3602746b5eb72300bfbd064deaf3e4f886b10c9923f13e404c168482dbc106b4c485d994278bb96daa58064c1da678b11c0360b0d795bf61ee43f1db68eda302ce31614921dd33a3b0642e8dfe320b4b16ca7b31837350e01fc3ab59fcd264de3855416ce7015b3cd9936ef00090d99a9421a4dd557297cea788ea286416360614cba70de92290abfbb4a5eacf710d194d1e72c4ba919c0e6744cad332ca749298c100ba7f9ee90d3a1a2de4d4f182ea35b9d11f51cef6b16fe532a5afaad32cd8e642416e17faf66f42152dd2fb5cc5c74d00ba6720b33fab5e448713028dfb61ef0bf35eff46101a87bdddaddfb53e8d0743131fd4174defa3c815dd73263da8419f209d68c85656c707c67f3131296e27aff6d2df4fbc0dd4dbfe0feb20fa60fce14dc96827e6633eafa68af63dd992801d0ffe24e836446d2f6883a6dea7acd5031e34955f64c698428d2dbad1663573f2d3752503e81c3df42fc7da229810506634c0873e72adcf66a71517c330460bd2ed1039c2df3998fabc25b54ba2fb0a7a286904cf6c49dad4d8fce477d4918d4f90e78984f487e4aa036476e73abaaa0a10dc9071d0d1b987768a0a9909c5749b11f0d5db92a3365cb02bae513d31adcbd72466db7043d9f7638406c8fe09b5eea363c043e6df47d998faf2d7663d1dfded9be727939a7aa6a5d6c22a670146ad864456e6b3dc13e8a60164bba3384f17fc2cea4b8fa4cfb14456132d943725cc6585cb8816bb5854006f475f0db78b2d3067e119a78a5a197abb8ae7aad32d4ba9481b1f0887b81b6885f3812a9354ba8680d9fe7a382088d43470d6c35274768ca0e60cdd7030ae941f108f76dba5404af03b5009fa6ddec209eacc74ee33f221607775cd6f1e8f559a0ca18839d1f4b80eec74594645edbe78282ed7630fbda1855ead014c901e240428f866d891f04cbdc4f9d7ccc942952f595a518394ec47af12274ceef04ec289351bde3d80b0857c73bc569259dc2cdc9a56cb83cf8756dcc255b8375c5eb8aace5825e89ec177c242628ad2ceefdeb819b20c6809d79db275ab70ff485e15856327330548d3128abbc6c20c6773ab1b64a610bdca2e2f86449427fd3204db6e4d0ede517054271415f7c3f1dd0edc5bbbf2fef2e36363dce86cc68ed5078d67f114321f8a1df621debbcd68617faa696854290c71e09e3b966faece8801e9e7c7a0b613adcb6c0c54155e90530eca623ca9f98bd42cdd4660cf38d7216309c465e5fdaccb2769a46c84546a35a81d496beee9e3472829d12a604eaace0a208503bc401caf931202bfbd4572599c371a78f09f9e2138f3cfe7db2207b7115a17bc8abafd7be25f7a42e896476b2a91ca30a45ba9d320fa15a88c65d289181f145544866a339bc282726a3a11c0f530c60ffd1c98bb48229e96fb7f432b41d1496390678180002b26fa9d83e7e93201665b663edde0e4c6069f2824c79c73a76e81ad7dd4deede5a832dd1b59d6bd3144532bc1f50283dcde417ab557e01d1422939185880339eea2b961f81b7ccf1a5e568b59bc156d023bd104e62fbba00d141a7a2483936d8279d00b0e8fadb958ba60e337750dc47b3e2af8f1c59f2087d4c0dc4ba12f27ee67bb3382f6596862ad38c564f285e4ee91bfb2a3f89c12d74c827233b5156f00b53127622d8aec9170fe86b53a69b8f2432519d9ff8a25e49d4b783514c940c3440beca31a5828b7a73416af55593846fb03688163f8074f6c92f11ae11a60cffc124e86da556ea13c506fca34c3a0899bd72a7836157fb21a03bd6afa7164df1e4a7889b2647c49c2c27970269d0867ace61c7b764f783da175bcc9e90584f4086056ae9c0839669df927da8b9113f6bea43e3b326b0c981a1c66f229f8f55e627ddf902be2eff09ac4f59052f6a39db23a02fcafe95aab83c9bb72df44e932d2329bf23b2c91399b03b716cb61615ecc71abe54357996b3f9f0f85bbd6c99a9c6292ed88d94d4b8bd7fcade6c7163e4007949402b507ff17be05ebe819e41691c39a194402e1a9a31785a75455950ee3e5bb117609c3942bea041aaffcf4934b516d15b6ce997dd81c5222b6f80b425e3d5de1ad9d09c4f48e90db4b47aecb71b06ee4a9169f601888d2bbc4ee093641d9b58a612e8497fceeaedf54879fa7400844aae4e6ed84bcd10ee62b8fbd97e3e3102b1348a78a16fdb83610a657a866512273e07795ddd61580bd29d9463f1d9106954f30907b78792f8e30637adef2d8f53dd7b6001116014ba06c6dfbb3c673db08c383d032f95c50f5ccf2dcf77c4635c21eed49a98a347d2702107c7ac4aa378f6da5da838f1cf4c37e9627b8f0ca26dc3deb98de84373498cabe748c6924f6421d86ff2d641eff18670a447a3b51941a2d3c1e56f80e7ea94994a9814ca9805165ee33e6796bfd2addccca8dca38c6420cb512a18a21e43103981ebbfb29e915a61cfc5a4f638992b7f2521d7f48cae9a16734c243cfeb697c20f6324a86d1c671e47de738e8dbe145c0362450d5e1678f15420d5e8fa4446aa226303de34df4e24a0ab47fb4efab379025897a52349c0679fab452dde000a17198b7c13c9895914ebc2764df77571c02de7494abb684d879096ec4c920c90ca30c4ef46ac9b49b7bb6298cb00528193105867d6aa0d93afbb2c6cf17fb7eb6e2ea88ed95bbfdfdb545de35607296be16e6c1b436720887434949b852b03191d08f21efe8a991488cdbddf7b3c3b3660277e6ccd51480e010e2788f70dcaad8e5afb2a04522123c211dbc7545878d9882d27dbf60a3cf488463da514fb2a6a44650d1e8f0e765edf5ff5774d44d9cc6ecc5f481c853e3e729b3ccb63c41b773969869a9732f7979252f20393dd5cb95f413089824a5729fc39c7aac0845130b14462d03fc1be920032a7e8b938e6089d5b81e546a71c5783ede5662cf6290a7cf360b4ec56e2d46c4af04ffc9468e33d518f052c168890d0f05daa6566dc0a96c83b2049445a522d0b2a56070fbf17bea5cc929d61c25ec500bcb5c77f7af64bdb16476218aa212197737718705cb1078f3612e5c1d33c6300e04047e88a762e27bd6a09ed116f29788bb6e962759ee7c365c335243faed1031dbb4c850b4cf82ae23e36033c5b9ccaf0710adad96d2421268cb2fc7cfbe84d965fd57e893e3f8b852210fdc8e64e056baf60eb9a4cf004b4d30afd2441a29f3fcf1d122369ba369ad8fb9a9fb113d6259329bbaac24d3d5827a4b8d4f885881959e21b819edd7d5321b80368bd5edbbd71b505005e14ca4f8cf1c1f94dce25ff83e7d1712f10a11aa671f82b5ceb79fb217560c50bf09bf4ac4142560d2fe6c422f442a0323b410967fc0bdc337e6831e3cf5a9102b6f98607aa51b846dabe9a67c7b220f2a9d46a4c5b03510c60de5dd43f463330eef405714db2214b75de5190ab82af9383b62e8189bb61294d55c12f9fc65019e650db57c6f0a732aa33e52399d90a88a66b363bfecb70b4d58089956810eede8644e21243d6dce461f6ab7fb5621102dfec1a622dfedc8e89cc1e87f747e97281a8f6787a17c91eb1f85d08ebdb7c64c55c3606f9414c5be729c3a55c059e01921b2f004d2fb555e64c3c8feca29f45550cefca4f4beb375eceb7371f6b3ee79728a0201fbf732add9d380d6a22954853c3c26b0b3145ef4d26350fa633795387361d8d64142e99adcd98e8e0558f9b8df27facdfac75c65c82706c00b35c79ebfc7af79e25643afe9241d0dd4cf555b5671d25a697c8cf670d3609b25e566ee71cd3f3840331b21b49460f49a2fc81d3bf4b05ba8e2eca2be1fe4fcd4aa1f636ae24cb80cee34e4d9ce72fcd6737ce7b3823ceb724db68db46fba3e62472ed9f21b9359d40873614606e7012e198c680cbfed4c018e10ac14a40b63c44ec45fc499dd0ea7b51082825ec415ccc02c2ad96622fac4af72a4c3abf04f088290d94d6d475f6c18d646fe6d2d5918938c2d78cff3e537e6e26124579eae8a6345426c6a98b8afe65c62cdd867fdfb995465a0e5169b8a612fc1d662ac1a37a08b89d7aae00d3f16b677caa4a8767feee06f45bca5971280c73226857912235e2e7b31f232628fe3c502643fede814009b56e412bc37a8a8e8134dda5aaeaaf2c3706e2dc9ac158309d86e7fe1b6ab39fb66d37c40be2c3171752fd5a463200bf1c010734167f8f9fcd63017aca2cc42756d8be24bd29b7f3ac660ec14c0d74dcadd0fa0977312203486be23f16aff62262379d98fee4d2976cc31010c4d22420f020ccd0e023c394ef2b66fa6599a2cb05ec096feee0a1e1f32fd5ae083fd77e0d087fed68c54c6c4a3a035f2a0923b542c5a42806a20727ce4e0e45c137baf24ed6e431f413c0d1c4272790243a799e8bfddbffd333cdca971fc787ff3b1f5d9be62c9603bcbe17456cbdc6ad1c626df31d8d6a81dcccfde083aa2b0809401c07fd9a203d564e735506e765cfb90f15aafcd02c86c66548b0cca1ca1c0d10bf681f86f4b69c094b65f5544395ec82579e121f2e47bce30f3cfc89c4d1bd126e1e98cb269a760eed6e767f3b17bd7239e05066e4e3631da8557905b8096480bf4fcaa9b69dfac6f666913a75d76b8f7321cfe322645796ee46f0fd1aacf38152a8adac239d63b1e9ef9e93b62efa19f976d5f3e3f1a23e068992eece34e3c5b1f5203335efd89cd2f87e91a7ff306e7844add3694c48d4b36ecfd508e2ffeb0819e560f65a90d9d0e7e44ea6638ee8e82ff04e00f9a87a083a8fae9f8a8afe0c5550c01ee34f0789412183c37b05dfcf1fe06780c643315238904fe031aa70c1b980bfe96582d05dadd3b499262ae132baea6fa54eb4561757589fd54fcb43873350525613ce0ca4895fdf0d179dfe88af737fd083229c2fe013958960ad7ed5dfdc6b5f84f533d4bc00430d4a30d3fba2c60c97395e2fc461f13a7b7efd31855a7fb06bf7ec4036002f0ed01a4038acb176ff037de52f92a7d972c3f81792d3c8b04d97f4493f4889e61fd898f53a673f4b3d40821e4c731ed4543cd5ef22ed171fd515e1353d26c83e27890f3587132021fb578358c6628f86d9b1b792018bd7c3d826871e723fa6b1415656b3d30327e957321166928e1b856026b357458eca05f29e5d24515bc1fcb12604d6b28e486f4b72474e0bc853218b9971191c670571b70554028e19eaabb24e784c260c6145c5e5ca68b5b8f16d8c89a8c738269d636ee2b42e083cd77c22fef05b89d1f97a56b9b70ac80ac467afb72e4e333c12223ccc125b560edcdbac4cc458efd025f0444a89cf19588cbd3776522f07264e36a60d9994d537712f1e30c0a7f08c78ed47d56da88a3e623215141241c4022954751bb651b4e235357271f14f6d77bcc0e3c2840831c13c5aaf2c1b9259a6585e3970c4597d3c1ed2ee57cfa21d87f937c1650c0961e1391b86e3291950e1c6406ce34bc1ff97d0d132b997a5c309b2b2eac05f890e3c1e8b9b62e01b783543366f111f11f43e9b6015742023a11261f278f6c2ba0cb62af59a7b7fd8994df6674aba3cb808e4a4cdf37c71a30cc86c82fcb63ff634e45ec05a21c25e994a7f6e00fdf009bea5125e9d38997d173bcd558861dbbbee8e91ba577c2ea0a23c78c3920eb02765e6a131d17d86b6936b30cb23a74ee25c7460e70c9a3a86681354b815652d6e6931eea9f58098616d2de08f6851244e23f9a70b078fd5dbbb7c293e7fc0dc5e4fdadcb685776b4c4df29ee89f74d6c9223517b6e3cec7c9b0c14b9cf77a8ac82e6026bc773491ab9dac1c8d06f2272f1d09ccacf0474f7c2905b8a40babfe24b30bc8e4fba3ac81634117eb9de6dd0ddcce67b102dbf5e7155594ec0bdacf322961e63882d35143aa0fc25f1f0e41e1f23334e6f8056f09c954ecd0d98da8ae0d23199053f778c8f6bc7b364b632a171ae902f30345f46a1f7da398c9428fa09ada6ba9294160b45db78252a4528cf7186216710f05600695cf23ebf9f37ea1382b59e79af16af175322a923b5182ee4b0bcbf116f6cae60513538bdf365b9783a9d96356dc4272f0f5e97360933280dfa271b9ec88648be5aa1302e1a5344c3de27155d6ba683f219f631d6b67d86e3226d4e7cd5e68274ea19ed21c95b2910ceedfb23d85d588029be700e4fec4bda22cc46cea706f02e36b6763e2265f7ecfad447627256d7a3a71c57c3ce8a2e900783f20ecf790bb7591589cc45feffc2d6c39809add0bf52b826dc8a0f858442b8b5666dc146c59ebeec9465774a4bdece859f3194dcc88aab941e97408bf8c225360262fc96c72f040b77fe09cde2e1fa4bee99548a6d0cb1f68b7bb2adcb28474b97f880843a467e1d365cc172d006b055cdfef6dea1c2bf74c4c8849f93f686b145e3de6bcb0b106b073170f4849649e71491815503105f7925a7c8aaa653194830945ac19f9216d9e89d841c41c0be6895d191632f08f4e8d26a52e550ab2565d9c9576fb3bc1caa428efb85548b55561505fdf7c9fe47a8021feb64fc56486aacd214f60aefc7ebf4b8c97f85c65a0d1d1ebfb821fcdf5e13e003d1903d2d7aaacead14dab173d35016862d6f9330c66d391c7ec9dda3330a4b7bcb634bda35bc3ff301bcb546b68be56995c6bb4a139799cbf07b2fb6420788395190f5a3710b5b9f42bd2188a5c97e117a424e78adf7ecd41948402d3e44e91e7e75e1827aa0b75376dfbcde95ca3f125322ae5f695b2cc8a815a78064d73c9cdf583532947e61844a4ef471955f21180928f77de4061b4f35166e58bc034ad6de99098e812ad7978ef8fb34c6a0613c5822afb8c2d7d5f37ad79ec504a11b963e219dc4518da49d2da94168f3a5891aec90fd44de6c00e18559d75a9748ab0065f16bc50f1731a10c20faf2d0daad5bac6d8bfcd47552251d3085eb780a2a80df307b0e21d17a39addd99f2dcf441bbd3827c77924967974e084cb5f58292833d627cffc0d09a7dfd5ce83257ddff726fb73713159a66f9cf612e3a10b688d50a1b290d50fcff4d39fd47fdc8abbf47f7fa5560b80aaa515c11fa73c0a78e3c1082adac8331d1519b1b8a983b5941ba4772539b831054fb8d658079e53b17dca96bb3628fa4bff9f17e6799af2f2a623a7f58da86137a5e7f58a6124e6f970848c02cf3d32dabc32063a3d48109c36df0707388d77a905780215bd673f3755896059dd2403b49bd03cc0cdd19b56216e67c037d110e4f779c8d08f1e8348de869b804bd1a905f091f3d94c449e8df1fddb5860a4e6e9fead9a6ff61141322b482b83f65c9d95833541e18d46d78cd91024be7435bf2be6dbf209c882f892ba5105d0947d5ac022d52e53d1f576ea50fd0e5a984cbf6f9801615e9d66d8717b54964116c3ab0d699b7e9006ea3c0b203d63de1abb8ea484a456f8cc5e8afa739aae4177a58464c3c4e2534fa6f99e6ad6c9f51497c740927ae714419947f39271bd112a44700bfe7e70df50441978537db1a8d49c10375c658463e8e12651ccec3c646af5079f6f43700255b7261665c68d9e1483e389428ab37ce5ea48bb2ca8a5c983eddcaea62b4a7bbf910c2076f18d651c96663f1afe9dc54489081de0f2a32e1f486c015cb21ff0d985eb60861a9b5ff4da77928793e96bd4b1fcaa9e800713b9e33db762dddcb9008fe1ab4d4b80ad324a93da0a61ebe932827270141de9725d864256cbd3a9c2f346aa49b502d2eb4a84715903029879bf584ae38ff9f1c3df1cd2d1204df1751402c83e101cdeba89f8daa40d87178626dc74a05cdffb04d3a074c3f408ec5f5394efeb8485a935290f537ac719be97e54c43aeb64b5446de7157869b4f214eac3ecd1adad72255fa3de6cff54b715a74772b91fd60db2a09a8c38a356d9011c01fd09ccccff47b25ca6b3169fd9004855e60e3c760861c03ad928c813b30b9a85fa02e55b7b9c3b363a65333111801440a50b60e0e3893ea4957a59120951f728831a8375fdcc08ae5dfc21b0914de76db8f3c608aab4d05f2597af6cfe94571c1638f9f4bba308e9933c6d594b8651ad34aa911c6d4b8571efc6814de63932ca974e669a38b5fc770c3562c499595762913b10616577ba1dd6f3d8a3b6ca1784b5116c4f6e1c8941cea411791aa1f3766a3896b05380abbc3a39ce941b9943850d54af5316b91f6b7dff28cfb114f61eeba76dc419a91e6b0f443ade59893ebb4744923d3c6e6df34bedc3e009c15c3d4f149f4a781fc84f0022dbac4505c4d4c34b906e611dce2f1188d39c2f04bdd58f8fe44ddf0d7a752b47f5de131bea163a8138012cf3c615a5df2516e47c33569366a31f58f34936ad39c27f0c2b34307001af25dbd79e799a5857161cbf02d4d1ce7b4295873f319f2dba14bdfcef64b486311acc89a28cacb70e2c8dfb75bfbf5e9e823e455ab90545bbe6e926c94a6ef04b5b7a51a64a8c85abf80883a7fc69795f15ce331dd689cc18b3e4889de355116211e316e102fa3533fa8f3e246399af165523b7d87161217a0711fd0d2cca11eca9ace8e31cb12b9f70384077f6458ec7d12f909bf517a35463da6c7d01260a828260a4dcf06ef4057c3fae4294e2be63172f780741e1d7cca51cc2589ffea731e5eff731018dfe46438969a7a82918330646789a212b8ec334e1b5cbca5f066c6650ed1d2202c09527429dd46eb31d380d4389bb4e0d0fc3bc77f2c44cf5736e8d73f44b33bc147c3ea84626fa0b70bd5d34519dec5497484a118fbee32a00f5cfbe4ba05afa0c1ca1cb1f21ecd8889948475958ffe1a2e2dbb34e7efae5b28b91489e2689dbdd691d3ab4f9cb9a9e3e30f2b3382349d6de12d00d3cef5defc9f7287ea103051392d05bcc83478769189c41fa4989cf00a8e9c806a133d97cafd8d7dff96bae282b05685f7d87264d273c1fdd183ce4bba415d027072c8dc5957a9223f1f0788df08f04710fc5644390b9124eff3396773fe19e91fb05f0262353dcfaa574e96df350fb5dbb8750e724b055e527230a58c945506c66435316ab6087f9bf82b7892639ca828fdc8093db91c064ed9306e030b91fb304712533e5e3a3f4100bae004c1c20cada6b5bcb8d3d9bb2b9eb82dd3e9017e5e91e5a64dc56ce96e4d9405050b256b272188cfcdd5267bfcb955486f5e875f8a3c7dd938f241a475b1febd99c5168f9d074df79abc82f0c4483bc4a6d4f89d33d9e3251546ebc98dac76640d54b2bee7cb6a519b0b9206016ace11f90c4e5747e56ff62007a6438edcdd876db96749c5bef6455d7b61021d0f139993d604b9844ba8e89cf638eb8ecc39b3e21e9f45c33dbfb28d623a450b280733532f0fa37ec587c33b3c71db1e8f609e2bfd11888d04d1837c54f54dd664ca18484160bd39d10cdf267b2a4470c8b3b3d318204ea4310e76d3e1466febb99ff358b0869677ad2fc35cf948f7439016ad249771977ad335a2bea57c4ad43d30a55e0a95109f9b21a2a1645eb8c1a56b67e246c3cc3c3870964dbe9a907b1fccbda1722397f74f9a880ae221bb85d6d21f9f682e2e5f699f71eb63362309ad30cddbe26615adf0f4454174131bc1b1e9bbc47bd432729de29a71d009a1f0a1f14d1715ef1134484b4c13e984a454bea4be93976b7af0af28316f1b95a01f63d63bac8cb619978b892ffaefd779efccaa3c59b725feaeaee6c2b35057f95db79aacbfddad641e6ca9269ba8411afc5792b72c3b6666edcd978ff260f4fb9b628e2299eb82f1d9055129d6faf22da28ac1ad4f04972a313ba7fb12135d43fa16a35ec9146d0a936e2d2b46b9b83d7db171d8ebb3bfbf79b24f41ea395efe59902b399adeb615d1a92fccc00c79739661d489ad35684391128eaae6af0d912881d06e629f034c19f842679dfc23d4b50688a18c371d3779879020ee7295d20c2c5793111363b73f98b4cfc15d3bc38463f527c025113ce9c74dc48629f5eb3cfa1e2c11aacba9acc4604f6c4a35f71c5361f7ef6868d3827a6cac2b2c3379155bdb0ff0f036aba927ab9c5e390f53c2e9d5eeb04424197484af294f9b928fc3287fb7639b6f08ff48e1dc247f4adce3e9918733ef2b7950a1340d66bcc6126af5d9d9a3c79b67219b721a2ae02c8e4eb02b2128296c193432540cd94e824517aec31f6011d447bc4d1247253e75c959dd7f42559eae855b7837bc5e78c80f79f6f86b9b9891c6fa672817102b03052cf348957d8d5eb6a0417e6c3ed9493b3f1d5a8b8305539b1c41f6db77b2687bf57f35b27800bf135fcd767d81680679e44c24cb1c2e7c02e5bd0ff6b35f330cbfdef190be20ff63064995c1c07df55bf3dab8b97554a90ae1d30613235cf2cf35f9f05925248faf10ca75cced3fbff8e070024b903458c85e36a562bab04baefa261106ca46af26d7a0f41663b4c2efe079613db473d2ab1122592c323281ebe3cb4285b872bc4fe6d9f4a7d36e4772feacd98ac8a0a9f404d26c7c114d956ec8778d66cf84bdb47d9495cda462c749479f5d2e0ec873193f4b10c05d89371bbad9137b7174e5d4d524965c64854dcd527cc9de08735d9f05247fb7f146faa4a09c7eaa84290453a7749c8d6f8c7c1033abb690d0f4203cf53fda1c7a5299c73cb49d75eebb3f66e74bbf7c257f10d550706bef1dada2a4dd2747463f14a84137cb7da71304b480cf9bb7ee8feb6fd14d865c72c54e54c1351a57cb5a967f1481e741329bc8cd69acb0c8a8ee09445a173fee8a40f53fc0d58f090b6953e51830b03a6fffa708851fc2a96901158c15cec7d4bb3123c70efc98c82fd448af8bdad65b43eee427631b239008446bde0ffc28326933b61ed1e3d87e2ec67c08925eee76fb3c410fd70a57c7f50d0969c629e30c7ed16415fd2a8d6ef9626bf733d0b2bd2c36e5654e09afe4e3bf75025626239ecf8f65602314271e4078e14c3db62691e197b59bd7bc188f873c12784aff31926392e7aec49030b58e4a9ad8ce8a8d6752056f595ba5152d0f6ce5bbd4d870f7ac70af6c490eff4ba4133f3f44e7619db4d73b09b5aa6cf5e1726d975b6fb525b8b3a96c4c5207828c9e720e2a2236a0ea36eb337399c9413f6716f8a6a2760442227c06c7b4661153a81b5e726c89f0c4ede205a447399260193ede2ba47148f681aaed53561f0fa6b6b7ed63d6e6e75600bba70504c478455e40d716ea420e489b1836272f07969c2426a1654291ebde58bc671e39995ece6a99e48a1a3eb6ca0198b293a3b57f5e9e84178067bc9e31fbe3cb1879af6f9dc023195f969e7bc5ea7e17ff41ee210b283e97a3eb7c0a6b6281d101ea68c67e3ce6abee4d4a62bb6a0bfaced373b090816a59b40408d0ddc53efad1d3a3a236a8fd348b0d22ff024a473e4e2689341e19f5d44e340ea2bc4c4f16e15047ae1bb0a14592262c62844eca17cf52bcffb3f0094be4318380415b4b9f2a2ea24476e8a6de87b836886dc3ccee5e6d037027f8953df35b3c6e692d62412aaf0c66af0d5cc1ba603f48d32e04ab03e6e5a165608a51f4db0512cf8859e262a76c2eb68ac637b41a138b6b23d4848cbee920a6602df7d3950f917489c9f84eefae590180bd669077209d327ed1b1b2443d58dfb8c461651d7ce7b6c12933d1d76988150655f015175b1b4ad271296fa9a9a1a6b76efe76c93b5494f822a2eca08b8c4360af8409bfda2eb20c9caa5b39870972049b6c28485e44fcf6b5193d06948001b6614449873b8bfd23433d6d30b7f0f561e0ec0338edde0c99220c022d5afedec9a63f148e33d0d5ef58d9f0f657a1f53af0d01dd5f9de4c5bea611e68d357844a07a4191dea714dd5561dd41d02505781bd9a923017d8a90709889d85f2f74ecb7eab5d0c04dbe82cd46d62c8200d26af7c43931764352975e619ab9065964a5cdbce4ee2ab525f71e69ea765c1c6820e1612414bb9eaea0d81816a61d2ef26167ed185c94b9b73e51a9aa5e4a6c9c32d419b439a5539eaf1ea519c1f8df619324b56f8aefb9bb5c254ae4fe5091af067ed5233274616c32fd204ccc652648a8143f6a8ba8620a0369d4419d29edd643c57c115c89605b3f10b9bb0508fabf4ca4ae72a31171f391486941e5eb338c949c71d6073348941e29d15f4719294f1910752e712f3f6a63248729779a1d7e1a512ccd3acb5da24e5a2261bd2df3e7d8b4e6266d2f2c06e4652aa0480de9586985a96c74df3dd1fb0382fe719e928d4d9c65b9e0ddd01e39675984915881b80a0c685bb410c05dd872a3bbbee5457e6876ba682902d80a9723b0de8eb8f422a13d9b40edd6d9ceab50a33d05ae4f971ef88c24e1f166658bc448e5eb61a68129512777899f70d6193dc6520bf3b4abf62ee613e7b74253a83aa7ed18dd2974897def2e3dbe8dc740833ffd0c63d76f1bee12568ac5f48284e32d4959b81f7f8b6d799c12e9164f26d392641ce0d4e0e84338fd133d842bf7dfa93dc5cbe27b8ac7a55cf044fb4c0d098ffb7295127334c15ee671e7174e961d3acad3e81a4e9e54c0cdbff2a386a3f6295b0a4c94dc28dd11529de49636675e8eb55d38fa3ffe8511717028e53087c0d36da698ff607c0c36e15b1e066d86ea22ef08d097adfeafe430e28c8ecf0a6f73b5f3dcf981233216b66c52a8cdd29417020026d9c3b711cbe9898038708bdeb00ff69d90de5d13025d3bcbf2e3b6c847be122e9ac7410fca0da9cd8e8c5e8ea13e08034ccc7a4120ce654780e3efe103aa816f3ed2a7685923b35278928d7c6889a1bbc964c14229b5297936034689087f5577657b780954536395d75d917ebdde7d435e393b92108aa272a0c7252e3cbd6c961596e01f8a1627744e2d51000be224ed5c2feb615340eac62a7f3bb68e51cdf90f4cb79fab138832644e108ddb127ca5c4389adbadf978f74298dc4d26d1bf77c9da8e5ceb9acdd715e40d106746601cc8ecc3161983cc2b499b74ae24420489d0211c2aff0e3103b4ca1f5dff013236655945c88a13f81e550ed36502f549e23165d27b36defb7c7a0b81ff46d59c74abe13db85b273d3c431b1cce00cee477e3d7c41b595ed074b6f5dce325fc0a53317d851da7c2ce833fb021a95092341b24e2f983062915a3b7df4d8b54e56420a43773661df129810e165bc6944f43a07f76c8883c9c7422db5109c1d5dfbf82c64edbcd334dadb7250c25682724dd228ede18dc8da12663b5a4c0c288249dfaa01808707107fbecc484359437e29446e99f0eba2bc5eb49f364b2594478e6c2087d533dbd15b1cd38b7347bf113fec157d5cc669e8a9e3a930f2ae31195a4ee92120dddeb629fb2e770e5855b5b67c588d1706d6a49fe5ab0ceec13fd02e89a320a6941199792cd9dc319094f1bc3767c4ddfd383269e29f2c4b8398e464c4bb8b8e6a9cda9d947c62a57b302a005f9950b68e50f1fd3e614aa0529172e779b7cc83ac394859f8b61f1a66f847c8c17e22e9c60ed068096c1a696ec74abb86f2ab10e634940a1894b134da48904713d3e145703a38dba9b084b097e60d006da2e109f058179e5dddd7d52334023d03859f85ba53ae6e1e07a94c5119991f05a270b3a1dbce7cc373b95defc9d068611a16f304783b4030317d6da058ff811512ee07776f1c1c47038d0fe942b5753e33ec83f2fbe9c5353f085419da03b5f7b39205a60edf5352715d2307db8cb8404d9efa9971715679e1091f78cabdc3de51c990ca53b5f7e6f38cf557d6df6737ce115811c6b8ccfba978a73d2417a0f26f49a11a396e5f140a5c8f79c745d233ba4e8349e07a149acfca81b0fe0d8c7feaf2302f0250fe589742759d7d691fb43ec747469838598022109b73afa01feeae0d33e874506eec8376e07ba912a88ff44cf07678f11ff408fe0965381a29a7ff0093e00ad15f6e52cac36dfa1315ffbbfae56f07e98343594c5738b71ce1fe29d7931ff64aa71e449a1b93dce6677bcf68a19b052c84355cebb5e7b0d64c6f28eacbe72db1a248573975a2148b6fe93827f0480773a621e89a73669f117c86319bf9007cd82dae4a95ce19b6e3890d5800e2a67a5171f5441973832a8285554b6e94f130104f510c35fbf90f5bef7c18bae6c6c80686ac3500567fdaadd6b848bd72bf254a0b43926895059aa1fa087ba07a74832b95394a1e2f8481b6430b473c556545acbe6ad753fbca91462abb7d6179804b1b1de4d939d0e692a3ac29189420257254d9fa7567e61564e52f90f6da71e4393d1854df8bca2e7460a0a7ca017b2032a425cbfa9f199d2f136dcd70cdf0f8be1b4c8a91ce20b27d5239b7b23253b6884ff5f436a0613e6de72dfdc00ed045986c04743f1c37e3887b12a4e371c545b90286a544c163115159eee9e598abb1e0194749833a179b2397026d7022fe41a9d0479b94ec9acdd871e3c6d06331a6b13733b3ad1a7619c65a4fb6ec8db35b3ef7a764012fa110cf190843f4752ad9e54c7f2f6696c16165dc7b37d057b00122365141319819f79d3734c05bdd021f255913c0058fc3fd10208701e0ccab25bee6546c036d4b7d608afd4f084ebeabbea7451929592746f7d2785542df03756217be359af66da5664146b7c0beb564bc690a752a60acf5a839e613d533281aecc3e0abde6df66d4e8872f483cf7e654810744d4796766cb81656afdeca413f61d95dac1f332846d81390bcd9e88e21c4152be51d96c6c06846ea897fe249ceb5dfe62e345beb03b4e9f6f6652e23098b11184867494036c9176cdff58e5e0f6a27915c41b06462cde2ed71f2b4375483ab5c78f6fa1a162630eb604e49b5e6f2f9b67fcb5301ef59da92d8a7d85651b09d4052edfa92fb4c8838034d62b5c41eb717ddbcfa07cc241589d021cfefd3f00b165ed5188406882377e6636c853c4ba793064ebcc2caba06de9bc1c2bca30c552b9a62761aeac8ce36de258f17dc75bfad23a000f9710c7d6f440d2803a75a4aada662da9cadfb4de8894681c4b1dab63b0d90a6a433ae109121a0632383cf789ed8678474742c449c769c4c9bd59ef2cde0bec88648d612184e4be0c1997083d480c3ec30952981396c75e962992a41e8c864fcf28294d27b6ec5fe12a9644025574b81bab0906001fd0772b7920e571f8ff6bc10cd50d9d147ac0543a87121a12b7ec8859095f5f2248047b2a813f54968159de2ccb78069fb0a99381b2373da4f65aef9220afc093be195e4aef36f666c2a70aca4e2f26f22fa65d410d38009248719fe52a0bff64c2fea54a63a629425fefa262dc796711d954962fb32da0a302a7cb45c832991e098feec9e7ac2b2cb55fbf113b71cd962b02f26aea52d7f0eb5595ccfd686fc7c9d4481963a5e32f054ee9147d5fa822025a438217347afc5b100fecf4ff4d90b8e07cbb4ea64c6fe4625d8d39bfe0afee690d0e16d71af8f26213db6acf7f95fc7eeaa6fc4de8ff071432ccded6be133efbb731aef8f26d0d0b4975bd22c0d1df19a68c37de9ed7e6ec688ac4f85690214a47f6ed413f9286821637b9848efe4a34f6f9053cabfd091202338fb95e52351f03c8c176a6ac99b4a538d336daacedcb590920266c12ee64c285b11c8edc36d818c92b27a1753296e318643dc13233a932862f726d011ba22f6dc850abd1da5cbd7444ac66c6ec179d890ee5e7f59b42b343dfbde799777983820170b16d288dacf18a054bc085520a54dfb15b22fdb72d0a9dca5a097c8b9f174057049c0adc52463fa8b23cfabecb8a04046642c7da7a6889430bda6833fda1005217679dc856553a040a27bf08512fd1123656d37a9636817f005a866dc92f8cc562b2211ba0785bc7d7034f6f0c1872c2ca2ef8fb369faa309acef6d9dcb562ea794da54b707ab1427ad4bb4e1ead18511fad40a695bad9415b9d93246901e204d92da1e83e42a47bf02d3f7e5b084e1a63ff6e9a43e46ac4ae48be91b1ad3c1662c2c19aab3aa72761f8b69c7c9d47017fee947a1db169e33b40b1c61756a77e90cb716d552affb4f908760bc1386cc237f8957425581cf525c069ea68e789e5a7df16c29e9a8ee822db208e284c38f75bb3d8acb81753dfe7583fcede7fe050f3e3ee816c390a8e37a82409faa281610b9f64d7cf970f206a8ce6f8b0143b04a45146c8adba8f0daead5a2db63ebac1f8eb3a211d7b282e0c5856779b18f57546dba1adad6694d0a1054bca75666ed40230c83aac8f9a24dd8a5bcbf2553a5c12f1a86c7e9bb7dd51e65637237d0942cb54e1b6a795bc65a937c4afffbfaaacfc786f3bcefc01b69f1413df171b799e9163d864fc9562fb5e0887dfe6287d310888e90a871c709cabd99ab6e6e7f865d96e2467e23e03dbc37ea06c159bf2c186d98ec21dd311cae852aad73c760012b2bbd0504eaee622ffd4600e54436bfd2992a0bfc41b26981313fa39537088e45c5caa4019c972db811475c0b2d59f67a906deb9cc8b428a0cc84ec39b46211d87c36e240fe44ec111c5feca59458e27306f21e7d6ac1bb0105757a3b63628ad2e62d4507234704ffea531cc7596854d1fa4f52e8e03d83db76023dad2a966907f52501f9f30d57807321ac3402d36d7a29016f20f6283d8352c284a22711f379b2545a126e0614634d98eca3b57b629e972c270d975100739379a63d534ea85c2ee164734aa454669f6e61f51b714506f0d217239292748d3e64f7a5658ca9521971eab318c7482e71ebb23ad4593264faee4c5bda33347a1213bcdb279aa7f80fd7d3756177dd120aaa37c22124cfb700a34fb840470840878a9c2663049297fe884ce987850ac37f9cb16de664b424b638a53db86c7107583c8b7ad923a194cb6c1e1ea2aaaa14a5673da74e8b03bb6e2f41a7baf7d9e20e492c343f23b180ca68dcc23d4928a53c52f216919428a26b65d94ea27cbd6e6e903c010d6b345284e6afe6b7f2b6d4fd5a5c6f2139f130a0c52ffb114b73327d77408513667c54e0bae9eed35b6fa8bb14584a2656b6374fb58ccddfea87b9f36959a1ceff08b3601bc548195743cf300fd01f4316598951ccf8fda15c79b64998a6d2350a87983243a65229828d1ca7d7896d4b75490b562c36b6759e698056e43c70712ad6272054524b142af1e908f62601e788a6ebb4e019dcf557771c4130d1ac210e6ce6b18c7d4814781b04a7497baefe47a5f0b3dc78d76eb7d2548c470d06dc53928e45881781d3abd93a780ac4681dd3d25631193984606da998e9011094ec80694ecca59bc6fa4b3b55010cfc86231d1623dca461fada67b16a8913e486e6b30adca389c020e8b332ede225df0e94c32a4454a35a192ab4450d8ad417cdf15acf35a6d2a96aa2b635fec4ef3e7799bce4575f8f4a19a2b78830a79867cb3c564dec6d091cd314be0bde435d82d20b8202ca028b0663efef566b7385ef3961b36ec79c2417e3cc32ae7bd6593dc0daaea419321a55c1e8bdb364f50270ce0f8f4ba36894043e8eab2172fa33951a8c6565690207b9e9275ee05876597dbd4e45951424c6c891ffa64f3f8809c622ac89e618cdde76c827dc99a6d975592235a39e9e09380b71e1115cd1f985b5dc7e19aea1c0853d509979b1fe79d33a550989fb47111e1bb8fa854e3ce7996ce195cb78c0a17ac584ec8df05cf115bb8ae4aecfcf16211d67a078e6bd15c967e0334d81c82d0a185cd9c14421f98a8bc8236dd8431aed68295b8c9daeb4e8fd689e5094ad81cbff61905abae5a67a79ccabc10f7c680f61479025c7b3111ae6df2342fe952cde8129f00e40c6f5ec662e6038dd2d94f884fb540126ee96d2e2c19cc90a671da4f17f3c80cc0cc88ec091d98a96f9a8ee1b625aaee8005db66f759b4fd0b864395b8631b47fcd45b0ad6a49de1acc952cdc415974102e89456e00bc1150cf28a4c8b1d811e3398795e8af0daf8310e3c78477b4f438971eb85ed47cff9d01a85790ecd66ce0a87ff40da70610b7c9c783a74a841017d41809b428c4250e193df72cddb6d13db1b2113ebb767444c4542dcf778979290c288c0d596dfd3c68298ac4079aaf2d9cadf07a0aca7ddd603e9f8c04c591d4e4d7cfda0fafc7f99a30ecd19526e4f5c8ec6403465e25337c1e893091e844daf3ea476a21d53c14e9f8f1c10a76814263e4b8432b9badd2cb87cee10b4c4771923cde6d32419eb68c457cc98376395db4bbe4f5d775681ce04d67f7fbb68bac9e247894dbbd768f5a2e1d0090c2fbcaeeb3412c7bc1d2a082144c1822f585b2014ad1c19226fc23a3118cb71d065623d789a314e6ba9bd7191f625f144a21d981b62e4492716cbc51bc38d62d5e0fdea4f56a56ef285e9d981b08843baec104e036cb3f506fe99d8eb0594c339947f61e936ddd04d144cc2039a27f2064ced87661ac813184eead972e177dafed09a95a6d5b38b900e0df8efdf8a41b755fb8533e469369edf6fb61036cb4d47354e591d609e204531d9d102faf77f713db9fb052374bb432197217ca4291edb0651922b91a97b7df86ac05c9df3da575f7e7bc2844943c071634de007dc1760a27304aab0382cffdb4dcfefc554d8adc3ac1ab5547d59a5a7af155c1bd7f571b8d0c019bc7a97d53d6856796823a48701d0c462f05dd9637b23e752a9b043d69e4127db691e3dec85078ac6aac0042eb4d833ded98104cb6ca566bb73a0b18f13c541aceb543ff87058305bc37cb8aedca3e380470f66551a6af5907bc50f7e0cc7dbc78fa3aa97e0e991d64cf5af36faf3ed4a45a13448241ee80d909f28dce6e718667328aa97322767cfbc2834daf5b2dba75c0eb85771a798ce19a0f7ad170c9cf13a6c116cb3acb5d40ab397aa5317705c6ce119aefcc5cc68b60f2af0a301969deab663f2b636880bfc431b445220d73d0061a8fd3301905a4a513f25040a7e3965ff2423f59d067749a92ddc8cacaf4747fb79db024728e1580b013980478692209fb11f6bd8ebd388d1cd6e06e0dd85d918a9e1507903edc151fa00a507070cc8c18882ca4f011a52f2e21bdd481f10120878a231a4410bd7b5545d9175705aed809a32464a29b5644ad7009cf86e1c91fbfff135ca487399681a6783a2e63565bb835b8aa3492535c79db3bb007a87dbc6e14d97a680f20cf091c1c76a0ae075ad542bf9eaaf5a99af0d20b0fcd9afd3905d86f1ed60c7dbeb1693da81757963c052ae5bec241e74334bff412d57d787a5d11e74f8b96ddc5adc41c88c8e093f5a42fc688a1435710f5cc5e25b759ef8f2c48c4e870705df1909214ea11b0b80cef309595477ba2b64d37064703c0b63b4144a4608e0852157f516e4481911f3a206066e1484591c4b78814cc9ea05ec715afedc9e2ca88c949fee73d4ce8468babdc0a0b26ea5b85a40cefc5600d65bd896d9b50cf4dd64f38e411ff1c23e6f9f3fd813a14636cd507b2629f70f8626a29c9947f70d45766a06742f32b2da78d53f2e73a41967e3acb95cea0050014546da26c34f666802ae706a0992f1d18ae5c09cb5b1dea8fea86ea48c001a1447f6afb494d4f1d91d43b5deb5769d7db0074c4c1ca63b23db10b30f5141823ea05676da32b205f4a0fe3f2bfc819f3a9526f5d16edd1d5d4e2d4b373e646b02ed8c6f030b55a272bc6340acf29da39a8ab7ec92c6684fb7cda5fcd4de589d4d56c475116686d507e6041df63ff552d56d620bdcf968b330b5aa887680cd9eec6f8bdcdbf7994c597c846ad3cd1d1414b97b3cc6c22b3d094d5c0f6059a87a379a04057cc910f47dc9d14f25d8e316227252bc9ae080bea9d4093645b3042d0b76dd2f22fca65d913f7ba92739c660b3345adf1a60a9833f37298148f988aa293f50d73f720477ee842cce46cd4e8b553be3c454931edeb9472152849bf9a1adb3b0bb6cf7ddf92ffc757a204ff87067bac458e283dcbe9f43d1d45ee351eebb4069deed65e4a993f32228163001a523744f7c99695e19a0e4bd96d1917a0182f2efc6df96bcfe8ab16cb0c741e6a7c832a5266b6fafe6b602ef78db96cd91dd5f6cb021f245078fe5179fc0967e1957d7a908c3812a70fad47326363e7276f520330266f4eebd24a1564c4babf08ad878784472c81474402b67ce26e186268a2091aee83347d7ea78bfff9b1f7861671698eca06cda1ca2f06c1399dc3ffaaaa3978f551f09cf5032ced9fd3ec8b99ef0e1b6b28a012f6a541d28fc38c8fb03d2cbcde22abfb0597390a422109b4f2cb810fe3e158408b9352fac0002b69040cbab5f218b94f784e7f9619184e7e494ced1fdc8bd724ab0eed023aacd5b488c77c0000d42ee1740a1c08bced8eba675a2f26eae2da4b1d5e839f6b9aa66def8ba44397db63e5a6f86c8f5f40416d9f01469b787da14a8aedbfd3804b8e5db9574c83448b1e6d78c3b0c81f3dd7942ee8db9181511b847875d2dfb1da04909069520fbd4175ba0f25de61b291e0dc28ff5c409443918cf1ebd2a7699eb2d8b52b312738bf8fbd588559571203c1f2aff8d5d6b7821923a574e81b3c7b3b22611b1e0bc8e7bccdb5f7c053a6be8d33ce4d7a6583fb1f6f1fdfd8c5171edb944226e2f4a14073a60a8a8a97c1c703004bbdd3e666c10c8e9eaacc851d4b801503d9d10e47d4376e47f68b79bd61d1aa5af69fb8d3bc399dcfbae809d40f843ad260ac5ee0cf2faca092a229ca89baad4186e3bed2341887db3bf51b7ee7960a71139811c75f4ce352238c77efc707beef3283faa783a9b2df553500490d69532179f8160b903a39ebcd6f3330d1a0746751d0bd5ca5d8fa9f0cdbdc3b5069068998f8e735c062ca912103f4fd4fd715a22768740bd738aa7cccfb5bd2792917fbb18b2d770b5f9de7eaadab5aacca05e7863eaa3f839815ffbd505be7689c632a08965e3de7fffe8d1f36c71d212beb9dfb7f77e83604e53be5b8d3e114e5440adac17d1fe676fb1e1730b83896a503b767cd309ff1c2ddb59c6eb6c1821a8d7594d172055cd0bcba3d787b88e51137c158bbdcf1f0d2d1fccc7f9054adaeb8f22d89b0af07e6550c343d72e35de72fcf34704648446dca267d6204e3ed3c78cce7fa36ccfa13fe1286d44665bbb4d62f3fbedf5adc456d3179f00e2cc6bd8eb9fb487ccb36e50f9c0be732ed31bd3a5b96291ed52778746fc34e194097dd094505ecfe990b69302f9b588a5ecd5fce2f9987cf4f9ed048dabfa94ca0cf87f1609ff5b6427bbdf3169ca2284e74fc0db37043fb3e3f75de2e5b83b95f95de6aa2b9d4bd1c6a503f11daffeb12980a5476012273e97f941557c8e9eba673a023b03b0a667c46b39b3ae4d934fb75dea670a9417a699240d889a310796751ac5cf55f99307b35be935fcc27feb7e1258995a1516a1788d9b7f55d3e28bb0623f3138a02d8905d5a91d621f54a0842458f49c0bdb834bfb79d6a832c4113dc1cc02d6cc9448be23ec697730b0ddba81fbf2649ee87af71fe318124439acb895c7424e8c14f19f57720d28f27e3887e394eace07bea283495993afcb8921ce83176d08cded268c1ec683104889ebfb1750079615db30b0f91e75c0a9f6dabb1bc39b044691e4db52c915702d0eef41871bbfc44aee7c887f50b98ee585ecaff190e47b154d75bc41be202cc58c06f4680917351db07a159f5055597a7cbb4639dbda7d4672d40ad2deddf5e4924c814671a89dbd29ed364ce73cd82f3db595aea1179c77e4d94471488e6e333147106cedd3cb9469b41320b178f2383f50149cc744568e94fe28e3907b1ba6659797fa1fcd581ccee1ce2e62c3a3d6ebd44cb89b74f9ad6f4bda3c57f5812fa4c4a290906394dbf1b1fa115d64976d7f2233e615503d8d7a010332fb5b51f031623a61147b60bb26860bf63ca0688190d1838b5405ccf3d210245e22403341d69d5c8b196bf24c8ea52c38e565f1f7442e75e78b3179e7b0db031604490bf4dc4486828d3a173ea35c95a9243a25626ec2dc24cf54520c43ebb18d078c0d136582fa3b53d2efecdf98a06bec829f5c864ced063fc80d74855b195b19edb7cb49cdde484c70bb89fee5f11a15cde5254ea861e6a183316669a74550a3bfaed47250fc90fb5990ba3eb55d97a3b100f04bebdffda838f59d329edf3b415949c2d9532d0c4923f841db0f78e64c5ed9f89d2a4e49e713556fe9de824247ac25556be3e0db9750bb38133822e5d4a9073541ae4e24ff38b8e74f6b66738eceec4a7b0dcf326803c88b70de4f590439a4bf6cfa0d0b53cc555d5167e3cfad61a24f411ece035f5483cc34114239468a5ce645641cec602690cf75a314fafd5b886ad4551d0ab9616d81f54cc6a773c90535899bdff89ad4b45b1aad3f1fca98ac74a5e68779be8b6881ab1334fd84a614529af6da53df892c144100ecc052b813d04d9e46f248b256d3cd35442c9e652647aef1825025a1225e666920d55d321ceafe27636981747a9736d3788b0b998d74f1ab0bb3cc0dbdd0a9474c81e317d5e1e1c92aeb71e0c5e93df4a2fdb27851e2f93b85d7beb3eac193b14ef80f0ec76e009745b3c7110eb431ced583380a7a4c138b2067a7652dd610797136a1a3205405047683ab9e77ebf342e46b9933e67407f4277bbf2e057a72389f297e7ccd266be5023fc90d783caea2a25314d890531be6154cef25e453134d680a6f4665a89f1df1b81c4be3c514cd68b822a015eed66214d37a024030a10f294421ab6445025616b0da580b93815aeae657d3e3e3be56260ed50332a1f70537cd23b0eaa856aaba1b01f6196c424a853e6ebc3a1ca5666d7a99e6dd0f1811578457264d71d1ec8a3e9d28b9359c973c4afd55f1856a06abf12e398100dddc17f71e2f57e745ceba4c729b4f9f0649fa8a9b4927fbc3346076e1b9115feb8e7c37051d4ae1356440e169e771bad4f0cabe54656bc13324c6525184fa6354fbccad8c2e06387531d65ade7b7df443fcfdcf00a5e92e0f887f5eabdb00bc2ef8dba7a84da4c5f374251c8cecc7272697d3fe383e2d1c278ba635d89c5e4d1c0ad548c5ab57e7bbb06f58bf7cdf6fda57358fbee4b12754c1b82dd1d093d634e53df5dc568b11ddba559feb76476d457550ec486374126b214fbe035f80baee42a7e58bc182a66d090f8136b2a547f6d217800351967e26f01d7ce071f4d80ca8e81ca66c7018c3cf05b2ca9f6770bec76d6a171b3e550b1c8597982e6de597685bb57effc017172040a9a8f8a1fdca9f1fa3376b914293951e377224b83b6bd813268b28dbb9739e67bacf4e76d0fd7955fe755c1faf6d173efd0638770d7ce82c867cb65fa50d951b02338f3f45e7a7e9aeb925a0f88665edee53954539fc7885abd209719915db8418dc3fec5ecfc52456fd03bc31b4968d70ce6a485eb3d8dbbd91e6f44f857eb85fd415f8a1faceb9a26c5b872cd0a3e17e70a33600c2af9dd052d4af5f633e574afd8802fc1a9fd8d8260b795dae9576b452cdc625149faae6f67601fcfae8c6984a0ad2be780e28b3fed17bd3a8e32e56c7f73ede43b1f7a27893d92db0d0089124cf40258ab53757c0d5b1cf045ff3984bc1004955b094b56864c8ca967704cee7abd00ce92be3e3f32e479beda7f5c08a25a35c3940812af1c153849ebe0e7102dd32fea7934bc961a1829f8c87a13d96e69211d75bd7bdf6b06d55825d98fcc1b2e4a4cebf3d72fa59cc9c0651ba0008a68805cf71f86b4cda183436e93503b34d4d2383491e9689e55bf3ba2aa6e832276df0d2919e4a6f0984e6c27ae696c64ff6bd3fc0694762bc4074aab90c549ed8d19fd02c922265ec578377b4ad1c9de53292cc4a4fdbbebf4ca933debebe24c4b51e17ed8132b134d6520e248ba46f13bf4fd252a8b8eea61b8101563486fcd1408761bd2474af308904079e8f507fd4933e7f8d7d3e2a30db2e5cfa61c4ac6a930660da976c5e9cf695bd73ad4384990a1215d1d398c3af6766a0dbc6b85f1109d8445e415c9c4e291dcf34574cd7ad616b544e719eed7a957a21b241f92c7b3cdfafeb954df529c92c7acb8ff8d34e46ea3da36568ea5f937378c98ca5893eaf0fcb3f29b129d5037d8f422fcf76423eb8af5e971e5ca595b72ae7343728862b129f14cd34ec8c3430ca2e2afe65df81fcdc94fe21b023324603a0c92030a014c772c8181843e6a39cbcaa4847d614c3cf58ec62ba16bfbb6ac60934e54916c095c2229de7c902a0b77ed714d78fb35d066d76ae4ff81229ec46b8fff26a009d0c0a1ce042edeb6ace48d24dc5d26dea196f4f2e5494b1f49bfb2854d61de21951e8cf4fd2a8cdfb110a49c90f14c9cc05af1a9cd7510a612a097afa168d0225980c04ed870c04ecf98fd5a545a85c0c0e80f804ca57ddbd0c36a43a51407055e8d3caef3de96090645831ef1d4ff02299af7dc1a8739e74c72895238377facbfe7162f129f0c78a4fa7cf47ef64cec0c68f6907417af8e22a0e8448554c33b6d063bca7951dce9c29336d86943689d4dfc5543e89a76e3cea475ec4b76dffcfd8a3b73f1d26cbd62c0c8eedf3ccce059ad99d6b51cf64dde301b1f8eccae0a8a9875be8c7e5796080fb2d63d08982a234afa00877f8fc71e7fb2052b204bcd83d09c7fb699d467e859e93f9cac2dd841bf7b29c2dcffd4fb656f36409eb1871d45f37a295422210db0c7af3566123e296ff0d6c3c2f326d07870f188c99a3e1dc01dd15222689a8d6fd73d41880d45ec232286835c053cd913299587867984fb4e7362bd4515e947d8f2023c6ef3cd6109d9e24296950e7824689f04ff2d0104bf0a75f6c799f75cd940c2130d2f4712766c77b978003d9df90878ef48c35bfbba0baf7209742451ed999f5aac7e0605b286cfeb4131556dffd0d51b7adf7c76b02782f1a9cd83dfa603efadff9d2a81d83876f61ad99abc7acd1b7b51a4809996e7bcc9b954bbb1bb52c975ada24d166b46d3d1433cd92b7405e844272b8cc7307fd84531ea62d285b98652183e3eebd7e7320be550e7e6c08ea818f7e0d32b042e729709ce6d558d1049f1783b28bb36a1751333b06f0715256582244d9ace7a3fe661e5dcf90bbadbbdf8c6d114f248ac0531302dc69aaa9493ae9da903647bf7890e5761afe15119560331b1f6bcceb70c6e13bc7b9923c323ba9ec27535254b2149f62b78af5e18bb9483a11113ea93d66cb0461ad9d75761530107815007ba9cf9b83dddeb189abd769576fe66d3fb44fed0352a79f71e5b51767723d4ba9e0b8a3c8c1ef84a590017c0e09acb2301263cf29f473c06af0e250cb53c7e9f15e9a52faee5704c05f86e5d22a27358234a0af1f108c1489a2a4149fc2daf766a21f75698562fa14ca115b3aa03f71ec05f1ed6c7933f77c49923c6473132c01cbfd5fdb49a69f7e4814666df25c1287f373e7b33b83a835492ae1d31201498bb946a7d390f3f350d5296551aff0c1f4ba3ec4944610c0805a02276eb8234598626ad03ecb1c15b95994bd301803ee66bad814189cddf2ca6d3b9918a9a1e8de10fb4e4ddc80e701beaad19d0ae3ba0964423c2a2d1c15618e13a32e6b9726c46018708b9b830cc620de975ed68fb4f413e1ff16168910d5a4d54a72d7ea2f9e7803f6436b728784aa23a325ed1131c5279a3dd7b9512bd3346aff2684bef2924a7f5cd1452adabf2f87ce28288f68f45e41fdd37d426c1a9da4082f97c48d00b4cb8c119fe5a860e47b676e2e21779ebfa14f20acad70d3c3d39655339eff6b90143ef425225f6858b5d84c17b22f445f25c295a7cff1a335eac73fee558977fbb9c64294e0802b379629694df03e885a9e63c8e49870d6cd5df64122f5fdfa0dc6606cc7179460d22ced65d01dd2aadff5b6eec0f80c1f4fe7e6a7ae7f4f88d9398cf2cb7e0e0a5bb9172b5e7f74468d8678245ad7a558e09609fcde28f1d442358399c8f20bd55063709e3a15dcf5630f5fc84a4aa711c313278b3bd1cbdb4350007ca510975022a2fb9df812f17f12e97ac7cc97020c90b915d6774fb1968be80140f7280db9de66043d6cb24587c403e6296fc588a77ed40daa385b1950ef08d72abec4e83db3bc63610127bd83a6097eb03e1ab45b7e884c4b83d3285d68d202b296d00765ffdc63fd493257e5015be7139e03043d5b64e2586b53eb85ebd405a5875b8666d71b6d70f991089191832e5ea2e7d5720d2807c85bce74924d50dfd644b048f7e491a879d25776aa4597c37f1637747b523e1e2d5dfcd8913e209f121442999a01b1d188f290dd5f17875a4ed9dd66f2336cd95b3f3ae5a8ca83cc724ac8364b6bbb752315286b15cfdfea97fe8df534b2446f76b0e0c6d2d3831162a5141ab948e6a4b4b4666cbedce74a9308571ba0b40d6f91d157c07515d741e7276060d74736446af81b9ed40b8627b399d1c0405ac933758702172f1f7fab6ea5ff08a45fe6f7d19c31839ebe99acc302290b9f0c0a7aefc3896abd1d0b86d36a9a68e255d52239933fbba72b66004d80450f16dd0d56a9ae2c0955a98541f475f829ae708c34dd3440eac80e347a2680a420edc1e49f000a2369ea5938ffd148faadd07c1555bb4246fbeac0a64a3d87b72035dd7e45c5fbfef50b574580ae0f4971be9e617e5692071799b08914f960aa6e94ad485864232dbd96f337aece98d271d604f47150ada68a17553c14bf8d6081a0f050d20a5c89d3bc5414ac196124b3825ec06a122ecc0363ef837eaf506e12a61a36984c109a575eef04fb8bafbfe19d2abbfc12fe4abcdcc27f068a1aa024c488e1c0213b0e7612baeb30c638551ea196e2e34f536e25655358192dde3bf47c4335735fc7870189c62c8a356a5de2bf43f6dd38e9bc1cac079ac73923ca8aa1409b3fa7deb375d5f73b91f0e08f4bee9c0e288f7c811eb0a1b1989259d40913d27e310062199326f133eda69fd0f71b84898c3f1dc068e3b8965f28b6d1ad48bf2515e3df0b415d7ce80b15cdee3555963f71aa66c5a84ecd8cb089ebb3920709a68940a194a8e09dc9ad667bfe18abdc64a2d79de449ed37310c579fe74fb7f4dc7762d7058bf70e6fe9e85d06cf1231c6a02a056f41382620464fc1b4dc51e974f620718ed3f7f4f289d619990c6b21c1555aeaa37814594a2c653a3872ed06ec9dc5ec5505c139398b62b845137ad97b8725c11c75f27ebf623475710ab13a98a5901f1b4369c0c75d593c3e247c209df93eacf286b1203e549c1da79385af2b544ddfc33705067420d954ad7a3ffb70f777bf15d9e0eb9f00b2a4efa195dff0efc3f939f8a82e5ca8c1ddb78b5ee358ae69bfeaf54522152bba8ad59020e4173719e8813eaafa86959e39cf3b5f2c7461dbe0ca8f137a93409c5cab321ad54890b46d8e1176b3e52d65bbf805e767d25f633d0d4b2d3f493a5b3659fd918d1f5e1a0be10298b3f3760259ebedfb0e582ac5aa1518effe632c778b5314e1404fd6533abc30f093f6d02f4412818ca7f7da31819569b7dfa3fc0420e448c5451fa28f1b0ce83ae996737745f6652bb1d5b80a543001ce0b36c7acec65a5739d5a8a89a4e2436c628980ba05b54b76b88a735d2584b84725fd3241db5c2bdb0ba1166cab55539fbdca1e346f5b984291940ec9550b22e6417b46de64aafe314d26f5e04604493589fb4bc144cc03772f64bef3a59cc31ae65a1b5d552325c6a1e0267cdd6888ec6dd79598df9ca3a5a803954c4192dd3db01d427ae89f9ff82dbb72ed13a4ce6561772c2b758a8a72795684d4ca3b5258831e5225758f41aac6245c92aed4d243ad34a4e6e4b62e1f426def6d0db49238104d3c544210f995f33b54280ced052049820643ccc9a319692e29641583de3d4a534d52b0ba93a8c96da9de34038705f9fd08ef4109a768907850b4a1f45cacaaf116b2770c18bd236c4d9eb1ea819eb81c60a5fc392d53ee2810206c55746440ff83bb036a6d11aa59c6baf835d9b09562b001e0f0a293413d2c17384d5663a0ebdc4fdc2048470729b008985e3d6da3896e9298e893e2f0b9c67aa10c2e75a3143ec5e8578545499210cd611e3fafe4bc53e84d97e0b9a519233def5b95c5a1b37328f7e17e3b97660cd91434904dc8d4b30df01d0b2a8f2062cd51de4e9b94a70cfbc5d50eec4692399d49984ac04aa75d188dc902193f7927a7227b94e6d462f93b62f7a547bad832c1e9a8dcf81668ff73b524e90cab06c1b62f865f11425391452204e0fba27bf438c59d983fb7570cf5131fab2f656524350077031831007587e05072e174ee04e85d2868770eceb1c4ae9ff91fcd047c3dee941719aff887c4b45a032b49df5f3f265f843df7e827f536c32a367966b68d6b5908422321520f6bd640fcc5fac0061b787a9441a25e9f4401a182684f33ce5cc7ebd26cccb0fbb9188e053eade4e919e520fa2c0835aba56f9e9a4943b46b7ea2a34a9365bf45b0183bf5df4b4026f010e9919935cec7d5dec4e98b7ddbcd01f333c004e89c5459860954fc3622995651a323a6f95a3b76c950b9d68e4a333036eae8da5dbe2347b2cf8ee8bb2803d4c3a9d065ee28d41691e9a84a0eea4546f0bc2c20c63657da3aa807c6ad3afa3e2a73e4580c7345e9d3273f0135cb4915f271e7dbfbce5561c8bce2924c25df68a6e4fa4b64980c5cf02c8720e077ba1e8f3cde93f52828ed0548cea53c92b9b774de7ebbf5b9006aa902b4eece12c112e56c649975036f2aa60ae4763a5964e6206890aeff8f86540021e0ac7804c1ad932268dad554d4f78cadd771b6d53986f86d05dad60f720a5df3223630361cc8269b187f124073d2785ec14eb78b5d8c46908e8395bfd7c8076a1972905c3282a630d227267862c48a567b4012137d418bbcf295adaa9c2ace91d8f327fa7aee992c6769268ddcd097c832ab39acdfa316e893317899185d889c1075e0fed2f557b7e57ea90a9d2fca41b949ba95338ea6b3c8fcb821f771b2187c65613b2ad678d8fdc559199aa55d58660192c591a11d8cb6f583be8093d1f011b7f0e647e0a9ce5349b30cab2c30b610376a6e339b3e3ea4ba286ec1efd4733f5ce5f8305aad83b54da54436776d1598012cb6d03df96188d651e504b037fc99424e3b0d8576a69439d93e17c45d393cb60973f40b84001e74b1ec5fc2abdbaebf20c4eb958575872bb2008b3936daf2cfd19f981f04963dcb248153f4dbc67b7bbcda7b86daa92da1e2c35a93d985cc3161fac3af1a9320a67b8a5b8f1e392c918ea3360193ba673a8a180e330a95472350fc045488a175a973cecce5ffc6f3da78ac8168c17825128369f6429a4b334f8c7b8e9c01eecf2bc95769363193cade3ee9cef7fb4dc6c93c36be07a7d15571726ff023c837e686b824f0f9705264e451cd8756e2cf08d2a33f4b90c01770496b4de51059a2087c2672e95ab5117202e19349c23980662d83ad722bbfa8df6d637d4cf5812156117e9c120e456ca2cc3123f812c66985cec753b7fd62a6534d8a9bc5a911249d0565e973b8d757512915f1f0d37f82f18b73958d5291b2125d3509a8fa630182c3102b09382091f466681b48a1917fba20b095f48578e11dfb31fd056bcc43582e44f7024f9e87afeaf63274beaff9d13a9f93d28b1e9690669ca300004ff39f3447295e5d6d539f2879c13e0b42e78edea6f907a54bac1ccfc0740f2ebfe17c8c35d71f2eb52b5a4479268871cbedf5ccb174d2dba077988752de791ed2d0e2fc144c6de335608993544dabd431ef185ec56bf477809f7fab6c000b835a046254c6e051fbfdc0c2ed5e3f25d271b185b152d1207ad3019ce0316173b59188cc8489d88cd2243acdf838a36adb1485d021f64fd4f1f9d07e276709ae1f0d31c805c0bad8860c44b3ae218069388404b4613f94446234bf5fc86961f9f277045987711faa51a1f2d13ceb182e231a91d8ba01a37349b47f3de7ce03158e3f689dc60aace9335b73cfbcde360c10e7f30969ad63c93fab3a41e35db23aaaf06eaefa3ee1311473b9ef3d3c00d6c97a2f2a640d38456fc1add8907d5a87ec02240aaa4ba811d62402b52d994cdcffd3b58654e7f0c7df31afbd887dfadaaa24f726e6e4a0cd8327de87312b51422766147ef81ef5fa060f5a9ffbe1f9d823b22f06e5bf8f705ea36d5909e1da8155a41cfa2dc6ebf67d1c403099e4f152654db75a8fd50e171a3c89b90e8e02f4fca0ff26db9f3ed573130dcbff484d002658b765bf5b47b3961e76cfdc873ea6da02b550dfcbd72f25d594c4973a980af31b3a52d7c22a716eed628b6559bf5e934d4c8ca7eac35e8ef9d5cd82c5caeb7e0ccd2808f395d2f92d057d843751b104a4548d97a5bcdf16b92535e4c52468f3292cae3cc22cee345ca6eb967658b1f1fd8c7157002a3e5d74b12085ebc4051b945210ef384f76529d083296d4528ac38113a7d99784c169087e00d8751e78fd9625ddac96ba4294e89003cc22d2d97ee8584259093109f7e693ec0185c6b0e2ea4364e830a3294b7ceccef6aa83d801ea335eb53a41ac55ecbfd9b3eb7f8c4110454f5d47d5c6bce3f108a850c2e5cfc9524ba8758e289fa43499139c5d7bc930ca35fb2bba966df7530242af8aed260793d359897ad5fdeb8117a76e26a54e0d6d7bac961c6bb54d66ad5cafcead12ba6dc269197eebb9035de7d93570f7821a12848bdadb2a635969e50b4da6fefadcc7de137c0194fc8b0853b4ce62f5d0dfc5d93c9e102c957cc230a708b516a3e0a11b9357acd8d2afa31d5a7c71bd014b50ffdc3e37ac828ede178ee87bd077462e2ad3f8c392d2250f8154f7d1b48f31142822249442b65fe176762966bd777a8714ec76fb8c66ae71aaa702646e80cfd8ed3f0da1e33eefa7ceef97e32a63081d1632a7b1bde00abd0feaf619a16d475aabf9223584ac3ae78a4fec1c14ba515fe646a032d2f2243104f60b5339d0cf7c09d20feeb1f51d6cc89991448ed228c6469b092030535a5ab5a201f22baf2371f54a4ae13f8be3ea3f745a16e11f22bfc47ea889a1c3e4834bba291abb4e68815643b8b7a9f7a9e4745504d57d70e62ecde389b8a447e186cf9404850e6a3efe67da5d00014cd01a1620bc3c5261d4675da33352aff6e9050cd9cbd4c5f69d6b215e3109ba724ac7a82d22bac3e7af15c58732157cb037586201d7cacf5f24b721f5d460ec92390f753308b06a332e14b8932b5abd9d7d3a9ed9f188eeaaeafa5f86c0852c3347a85c936fe77ae3a9063fbc26062a63a519399846e512f2df936ad2955280760e7550e66b54bd0377e484272192b59655caea62d91d8217dcfce62286a1ff79e63611e497d57cc72f48a267b0808848a1e6d785ad86149c4cba6f608e857e5818b7e9bdd4f589613a5bd597cc4e6fde87bf53d0847f2e5612772fd12a34e013159ef66d2f463471b6afe44a84719f0bd59b7b3bb29f2cb4d5d1b5d80e2daa11504ac5c406d37fddce61d7af8d6f5ea6db2cb0ce3c21c6316846ca27b530031db1fd0003d12f2da97b9eaa09d3a2fa3dcf258fee8e3ae13cd9ebd49b72279deb02dc89db19f5e774ebd20382f4a62ab464d96d0b40c5ac3be958d5eabea8f41e588798816ec9d4c5450afdfc054f9be5f193eb98460a3c7c2d08f16b09e1e6be061121b161269561927a72704ae4045d0ad05e2028e57dc9371778f2b2beffaa38d2915de909de6f5f9e89e05a9c2cf399b6e4c520ddb93da924358da1b5b7d11479ca98fa0c1c6ccab247a42dc90ce1e670350f80ac091c6c87cce24dcd0b0af8ad5e981b976e88849c6491391e1b08dc531f7d8a963e048270b47f302f0c2a455182bd0eac7baeed2e3f03eee1c03b27e0da2b62b5c401164757b8b2413edfd0d9a0435ded666fac4be9035bf9a1b6703e1413006bcbef5f94587ba2bbbb1c419d03038fc2ad170c9358bdef90d3437ddd7dcc9825358cd0868b4cbf969290e95c8344a6e3e85b340456cf79e982fdcdbd999100c80259087271921fdad15e08030aa4a7e8e5bb05d2503a82edb219313fd250b45c5563c9325ef875e33058d853248146e1820216faf132c0eeb6c2f5c7805eac53ccd8b6bbea3b9af5956006ea592b596eb7c136b404625ff575b421ada7d147c7a1d8acf210ddc83ed0a31142654bdc63021b9153c082e3d165ad97f7cd0878558ce3edc488a5d6d821e2241e7060d492dce8cad6e5906bd56767ca9452a100f65fc2fd7363bb26553eb5ed9edbe0c772ef21b15ad18f26f0f97908bc297f271917d0e7927f6a4d2e4808ce2ec3e48d5ae7b726292529d056a63a82d68af147573224093b2a2d3f49acd0aea50e0f0ed04d075d92a6d0c4c80e1d753ab8a9eee4c3324afb51fd428a45cb3a2fe9652258a2a5b121b53cf8b15683368a34d132d9fade596d0ebe0dc94194781c04b344edea023fcecfb773ea48e918a706695cb40b5bfe6d46f0d1a0ce25c667db88ded405011ffb97f0e7ea0f97a4a354101f7f0d1deaf3dc05ca2d2ce2da4dc913185af9d1bc81d671ee3da431f8e7d0718aad687504e7d6b7782bc8e0174dc7adc1e5caebe8531afd7323f1381a22a833ca1ad2fe94e55acd3e406bff24b07d03395bf543b7de2894dd42473dc49a4b3f935f952e22e80a7e4effd35ae4c2d6ecc65007f0283ffd040163bdf880004291ac6c8abdc5eefe6f1875578b576a85a77c496c39546acd29c95573be7b879b2d37722ba242d852eb1bf179b0d28133236b85067cfd844de080e638db7fcc87200d5cb785d5ca38e84f7d8af330a8405c4078e27d988165037fb97c467b8c15a4cc171739541b5c4c4a310a148b252ac6ce716ae6ed83d88f53548b3f0d3114c1be4d08889c91ba97692a8ba82ca711e2d68916aef681627f65c9dba6bf7868c88da94956bee273db42373122d724e9c68a10864e6af700e1726831384282b1355672ede67acaa605480c84d880c9efee436d4065acbd2bcc50f9843fed64f23e709ada231159b4c4cb82afe584b676d9f4a8526bb07a6546b10378fd878bf228a31eed77340208d37bd42b6ccf3a9e0a5af175963033ae2fd926e98d029a5b1c157bcdd55e6b8614bbb5a9c0f65f3630294d82fa77eea68a223520ab4f4da693542b7548da862e3586e3281042fd48fa961ecf66933eecb2468425f41ab8d9f41084b305b49cc2a8219a01addfb1fe53c9b426f0957c386093976092e7a604b5f23fef4980a8c4026402be1a22fabc185cbc468bf3013d6a5a480c0b1e85fd48feb089bb6557a6a38595735bc54a9f07e1bcad1b907b9148dc614c74099a9d1d25de75cd276b8ea957aad423bbc99bd0db4e414b3c9645cd5c0ff303cc3d9d326a6a75691758bef2dad39f438170585c8089afdb015d9768a457cd36f87fd0773e9c883a017f3b7b58c391aff8b0c8ac043f8bbc16085daa99baa1b3c5ba3997697ee39c172c41314f982b4f4a6fef822589a5a8cefea106f697cba65df3982bddf5c36d24d2865be4fc41726fe5722554b5d1bde4f3f068c6804ee54a2a97b4a5a8505a96da43a7e87cd0e8a8e13febf96e2401780cd3f9e7fa512b7d71907d01fa6c141274c47087b57b2785a94316c1521b05d65d3f4a1aece1421540569baba3a3495913801d35909ab59e05b361c91c0d29596a23fbc34f6673029e902403de1276172cff69327e9cbf7134f12870f41a689def4807f06d6952d2f3dc3efe76e92fd6155ae7d0d8f7ab7c87229854e3ea1abd0c28edde094876e24decb83dfe8d11a425ba7fbdd31e04816aa84ab87d8662c992803967c1ed3e4216d10dd13651006a529f36555644e26ba204507702c2e815b76747878899f7f858b42f8440e90832d00b51ef634e7ad9b1db1782e36941e9d9a2c1dff699f84df55cc7804347876066acae0e5df2d3b8a90e724e18e8756e68d8742be257b0920c45bf0ffd1851d395514bcc0812e230d47b6a3a127ec84ab6c8fb96eaac15a8d702d6c94d941f53ec49354a3a10aee062b98281adbc9ce9602671f9332fd35d6e027f96cc795c38c218119d747936725a6462557ef7d1b3591bbb5dc467d0adca1736d82ba3b9f2a0d6570a86de2956bd8ae8647a9fb691937a8ac5ab391f41b984cf89369719d8e2c4f626db0c554df2a1713cc6b9878882fee3497a608f02859bd89abd90ac11f02f999a12a37675b8f440ef58c2161a2fd4f2e495972be9b3bb405a4d5cea634b07ff8e89f8e53345de4c740f1dc124351c3da15fc6176cee07893ad45d4c39af884e203d2b0436761e5d29208a038b02c23276d4f9bb2f6b0d7df0b8c854b7b368757a5e97cfc3b408b6ccee7b49f277ba31024476d2dff91c68213bee6c19efa474cb7b4b4a38e49e212258493cc83ae34ccbb9f38f9b7bb2afa6d5b2b85ee94e0e831e9acb296a0733ea4acbe2b569609d755cb2e9cf62692c83fed5ecb3605583409db7aaa6b90e2f995afaed4d460d2acd8f20cf8263418568dfc1c5eb7e0c70b1583809b01af667730e6fb7e3062f1b6477d9121e1b35833b4a5ad66833f4973d5156afe355c6e22643742fc1da1402a6f418cc5a4a1eac00ed1642cd3a39204c91cdc75ca0a9b3eea808c022534489edefd59d78d923621d1246a62b02d21106be2210bf4b5b2e707a8e03f8f91412c61746b7a1009c109aac051a8ac849d9863a548536795d3af683502d221fcf0dea710a855a5afcb5787fc06f7eb6ab27b5f32be7c1e6e3ca1bfe300ec6e0381f97ac2804488b74271b4fb0c1aa5caefc43e5056a1909ed67642dad239ebae4827225eadacd2654bbb4c34a8f7b3133c66bd03126a548b8bdfa8958b87f1819c0f634b1a490d8abe561466075957b281fb4a7bbd2060a64b3bff051b9599f67a2e064c0a89288d79c87e314374e8c90e2eb6c302d01716af33aa43a0026d730ba25dc23a1d6f3d79d955bf57488b5d7d7a78008e98ab0fc9aaac514fec426df7cdc957ccb364286c73d81db2bc32f2ea0c4cd2a33e2dcc6e854182765d254d9db0980c2bacd6935403eacc1ce542c514bd69ebb81fdf26dbcb9a62daa164d88fc752e3bd0afa7d4c40d6628cc5956afd23f142333eb63e2020cd3d55b457d62e01e143545789af8e795296df8cbf030976f81c60da026a26e1244d3682ff66486417c5f27bf0fcb245213d3c82cbdd2ef9d3034a7f3cb429f387e96edeb5ca826fc80d40fb2a7898a07e94d8f6731c776871d8c91f4e79ca4b8ca90b880beee9c1a47376deb65d12d2db80421eaa14cdc1b385c7bb8ea505cfcea87fd3eced425068be789b7768c280a55a0ca977fd5f8ff7daa764dd9c604a3a899f911025f6bb6c618448d7690b129f878a50dc19968fc13d278fd23cd97a9b8825879a2286ad573db21bdb26fd177d7ea1b2473f8e9dd4facdc0c07885a0ce790f4bb79e34ee3b6da2950d6e45472d5b3dd7d53b18fd1c2cc2474164293df1281994e76f9a5f3864f545ee6d07ce12f68652a16bc515ac76b9517893a8fc8225b18e5d9f58c65eb07491348e15ebade1b90be741cf9e3fa250574ff3b9edc148dcac2968683de364548e3e790d9bba2a6ac3584f8223fe32d3468044f392107692355cd850b1bd31b6dec20bcfe7a00ff05e0be14f421f5017800be6fcaf69bba5ef58539b8735ccf5010e8c1d0cf5a24952893c622a268991bc2245d0126914761b0c80e134de01c8a350151f86b4e6782132c9d3b263a7364c098d91a147afbe1f25483317ef5736e07efdd526a9499ae853b2095d55d1a04d7b851f3e202ad47b399de392094c063b9cbcefeaaeef37e028dbd7691e0c5d5a4a27ef9cf945f63ef4cefe7a16eb579d504980cf401d42f53eb713071eb8ea4cb6c163978d024c5b46890b4c5f7f6cf0705ef1f5788d6d420b04aecfaf25760422c3160be4d86a206b3f0420f9b50be6e122f8725d2031044b7c0ec95ea53c27558e2f6c9e04127f82db83a5c0c8c6ed0098b9954df11661d611083319c8dfb98da47f98b36c143ce22565102625fe1efc308cb17f1ae929db5b8a0ff75113c55b06b9155064bca35fdd3701c53a888c880ee82cc1dbdee4ca11dc4b144ea5dab1fda9319562bb497a0b2653aaf218cd32fa9245894b1812330a3ec84b18c7a9d6df93fbd99b0aa77839ebd3f9c8397b2e5c211a83fa53c1a3e60aa4d9595267ffa8fb7e05338d14632a5b4fc01f463c83f46aa4896d7cb668d0d657e6cb0cace91d0a9d3bccc7b45189efe823b5acc013f18f9d0788146814af9e00780a14def13f72cd90c2e6251c0e81e0e6df4cd169e8d56aedc2d004aa511a2004c6585753f26ed976fcd284c500813b4069a6b7bee67fb68ddfd4c4f387cc165bca605c7f0fa473cb3be6ba82afe241c8428c3fed750f8a47e082c64974782d9b0c620658b504289bf7ec806cfcea160259088eeb19cb21aa938df7ee2156b315ea39559a671d5d38ad90e4d0a29efe4d21ec0faef732aa5bd3ae0365dee4b4e54658f0450cdf8da0a55dd58490291d4d6958d1cd84725a545735613ef3d50adb94e06e142b45b6775cb34a1ef1b76dc50f228e88fd36f16c03c9a35c715c9a781ba4df7c25a765e68e913a504baf949fa2c17989b83fc8d88a5d2918ddf397ae04388e490834ce59f6804f84c598eb6088df573738c4ceded2eb579f3dfc9ed8055424bbb561be3d403f18b4220a9868349d66d0f66d967c86c3cad2974e7924f7de4359f320d4bf4fadd49068e1b64814d2ba68956d65c3960df9db88dcfa24978b7fa73c64bde8642bf40feba4def625c1bedf0d22e6ca4f3d7f092167453dc480a7ae905fddf3251ad65e07516ec6c1abf2a79f2f4903a07e5c13b1be9a9aa8f3b84fd769e1351627cd5f0f7b470b4b017b5b7a0ef29c20e951ac67a46c2d6e453c73495a4de9ad51c3cc62b76f44d8c24c2adfaa77390220a317cf65d084ddeec24758d9a9f8e0304ee03b8325130760a13085cf423c76890401512ddcca630f6b2883360969a243d191e318cc89e2c9a92708899b6aba45dcd0758408e263f41ab7e4d77130112184f254ef4e4591fc3ed8d24e8c84a337c87b0f08005b5ee7abdad808164263289cd2a192ee017722f9a524d4a58e5640636955c3b9648585da098a06465b1f3ddf3db029731fc0b716bb0d583a992b9ffaeec18828178d93f04ee4030ef0bccaa3a52376632a123b2cfa4c0f455858b9cff1cf2ff9d3eab7280282da01b5e6932eba58529a5cf44a8de70ae12445b977c69b72ce5a91220ed50e6e0e08e682fff372e98290d518011fe5375316c4f6061d6a9d06ec31fced7319f8ff5d7f57fc3b84e9ee6dfac56a6c557374d0b6b081348e7641ee8909ae53a91515dc70046e2246f4e83ba3552e90ea9907de1b81cf50e014ff3b80e248ba4102616ad641448447c62d75d66ff4da783ebb0339f7db22e9fba93cc88a4ea02834b6847b5c0fce8187600284d767444ece340b4232aeee7df3496b2a4df9790bbb3b5e07f869258eb02215564c841db0cc70ef357b57a6a38d124f4fd91d2fce927c6162437db9f531ede3122a122c713c489243569d27a19ce59932689182fff98933d0c29976cc7697e8828d47caad843100969aeb707d78bd0475f8d9602710aeede5c06c76a2e3ca11bd082e41d64ef2292a015155b381659c097d67d7b6f33876148cb10e4af29b69cdfed25df39f4a769c97c6481790a9e846c4649bb18384dcda13a6f6d77bd4d7fb61d4a0aefc99379fe209b9b1c417ce7125fc1927da027940f513bda661a78a91deb586f3305ee74d99b2d131fc8eb78f1d5ca79dc8b846144df1b3ae39c6ae1d5f31b05fda627652e6a6ac37344d6c54766e5892e35139f9814054e57d1e671c8408aaa374a065532701096dbe9d727188b1107c6e9361eb61ce31ec062ccb796f8ef0f97822f66ce1d00c650497c7a3b3bc2868ea2d2a219fc5bb2a33003d227d1b98b1bbb42ee408d746d2b9cbaa54edcc318d1c74bfe73b3363291a6167e1a3d2363b15804fe3438bc8d1a2648fddc30bc42732c5ac6e004108ee7fc8f126a8cc58692ff5376df2efc06988d877483603da22f4c1975a1228c38cbb195e026f4c0986d9473504f1f73bbea3104054f27577ba1ff14c382822dc7a2bd0cb61c42c4ddf255ae8cd5f1602f5dfbff5fdb1fecb7402b344117200341e6b29b0af13c6e7226fda5ec954a5f1c588c5a4613d99794500361ee5ee063802fcff30b5956eea4c3377586b5b3b216f350e5586edf08e2d6dd742db8bc813022f409c3d96e42f7cf3baa947116f400b5797ef2113f09349e8d008b29c87755a1c2f1ac3b9fdab48ee1013be051cfd870ffa9c9cea33a97e64f1b1c05456aee5657f481320a6519381b67327ce17de248e6ce5ecd95dc8182e0bc07b2dc36fd79cfa37acea6e93d981b66d2c87481e6881b6e4144f5ff74fcbb67f7c153815cea0e31014d0d58c38a4a90cdd402424aaa355ef9baafc782d4b5bfdee7ef01ef93651bbd9ed02475db6a55917c19fba1e4164d92ad83ecea284cc24363a309c44ccb5e2219e769e97f430dde05ea0052319d2023616341c14de2cd8497b70f12ad783cf8396290b66c9d45bce6c0f691bf7049c748d00b7f7a33346f483d02405e01ec737419afe51e6f77e180652510a5eab80e4fe97ee622d62fe338077b3c8a15aa3153ba4404bef673ff9038d35b25119341f0bd11fc8489370e9d84a929a5c6aa6f94d6d0f1ffecc88c31efd050d531fcbc24b792e4f6fb216f03566d2f5c95924e83034f3b6bb517d3afbc958198ec2183554f8c12dc87512d117ffced3b64085bcaa9c2f29574d34a9e2d46fce071ad3354dd53f1d1dfc5f8eb8f5aa403a32924130bbad40bbc0f6b225d20c3aedef052dc7c50ff76b636a24f48e314ad86371502b48c6707b37797fad565066a424af781a009a3ba50a4d2e5cab7776790f324f51902983044a93f1d1bce058480603a0dc6f2c022a1655b9997d65cc2592a14f71936ada7fdfdd942dccea0da4af75208c46563f0809162b8aca7570c6d8930462d2edc1542b6015225d2ffcc69f6217375ad2a89e91d631ca5d51b7f7454e9a60a39b8415d85d8a48136e10ada98bd7884567f840b2cc11f5aedc45d507addf4497f1f3a1a5d723ced748a5c6be008140ef05d7ec90a629865de715507b993c6891e2760e5f749ffa245b9a9d5d11ebaebe0c87c2014b4b1a7a1b3be61a1996e63c028c2fe2d1af7226ebdb60b4a4f933447b57b3e296eae5d45c386f08bbbbe23251b64e1773c8faf9b76350f63839dd8a0364f4cf9e10035353a9f407888f2983f23ce43249d69ee514ea80780230a3d43c526a3f3bf0f1ec05e7939635fd2856285f75defadb318f151a2cceb26648ddf995dbc2da799e9e3875d5fcc3051cac5d3c2755550e260ccad01c7784719421e449e67025d918f617c1cbe0b669e3fe33791ac244a3677a8c450ac1f0d2b65c1a3004a5d250b72925fca04db083cd4a870fb1b61f63c0439ec42cd6b1e6f9365ca7e9b76de839f71898a52d8516ffeccb041697f34584563ef1f2eb4e7241845b487ace85ee252fdda999be63e8f27d6494aea9e5bb3ab76d1e3d6473fae9e5babd412238f2c69420c7dfae85149a43424a7207646ace73720594fc29dbbf9b372525bbc826ac7e27ac4c2e6c7d7832cd4849f290cde9d3c15f4d712f5f5aabd0d9adcba55ba2dbb96b4d1e4457285221e2a59872e17eca048b4764ce1b3dfbadd9926e9379ba40a2cc6543e9fa09d28521cb33d05259e9634e753e79fe9024ce55f8707ea406f28fc22da1fe0b970a90b3367d13d10786ec26d333d187912404fe8d289caf8f13d722b65522ea3b7897579b978b359cb87acb95bd9908e7c23d62462c257866dfae693691857e127241ca6e80e4e740ccdcd212f5da0f741e33916fe234dd32d105c32515e9285b826292d60ff2d5684d19adefb3dca527ae87e4b712fc4a4fc345f773d9920f0f75d18ab91895a29fb330608c3323506fd4c7b40ced00030aaa5012df00691c54ca5d32de771ed118ca496965059c361019a856607ad2c333a8fe20ed805a0c6f4e0eaf674a1092273ea3ebc4b6e6ab0cf3803c83257565eca73fdb050f3782e1a564050c33ad29092839ec78a6b81a03756952bd6398dcb1d6a0f7f88675acc017d8751b72ed7c3569d33dc760e027fc21c6c042e59ff0c19b863bd23a3acfa59bce27103a77af4768b27ebe4cec21d5d1648cd3e4460ba622be46634b1ece465b71537029647a106e122f44a7fbc816f52b35211272da6f2cd9d3ec0b05678f3cb9dfad840db1f1e4bce6f5640ef051b03649e883617e6fae2e7ccf20bd08bf0011c4adfe74004c3691e5d80064478c60d88e94e2705676d77be52fdc9529692884fe323c6be7ea4e0e9e3f2502393e8dc5dd72849469eb00fd2ed043653072a3431a7dad09514ed304915ebdffc071686f04215b3ce1789f936b3a61823cf490d20f210e84b73631d69307fca38ef974ccb87b0152fa441bedc8e3c7858e86816766242df4536dbaef78e755dc99d216e1c64028f7afd9bc8063e3700788092ebfd8f6211312fa6304209adbe8e4ebb45f553f67fc052243547e542aa001695c782119c2772349fe8dd4a09da30b63b9e0f6b07f24addca3a83121e0ed327f47f66464b5256398287e1a732b83bcc1c879fbaeb7ee52b9b718cb57ea629a3a2d844ca4db294c4f72b1a34b44b9c67bb52afc318b1bbc642ee081289c82ffb1ce74f03012375f42e2947825d1126af69018a65371b2ef498adca924a2a6e1953d3fb39392c0cc4caf358bbcfa9f1807a17320162127bc18a302d0add8f4613f775dc72d6837d361ab76a21dd189a3ee5b68e0a89c1b20a1e0b41e98671471a5c63d158815a66e26c2ff33054d46f6a1c22499b00e78fea7ef9dfb418603d9a99d14af38832397d2cc91a57136ecca0050c6747cd0e4433da402328d1fec911294fba9ccbe25b710bc5a406cba238be4c817767c269fc917bcb65feab26b786d68e67f08163ec52a7ac5fbe842901766011bea67549083b5cdcaa7d4397a3b6275331688527ca0876a400d469a9ff88f18ff75c53f69671088704bf947fdc553dc2c2e394bc46a09b38696da2de0c6a19632640503050a6fcdfb8ec0fbfc9f7a71c49789d4337a3da31bd8be8bebc528597dd0a16b4f2dbd8703d065019fb9ebf3be5e6351408322417f60fcf90baa45c532fc7768be523a5bda9affbc55a831040ef5a3392beddef522d136ae842b13b39deffb1869363fd591109218a81871236f36322557ff658a85fd1073baa7dfe22b1294109c28fdc1ba69087b5e41cfdba4710ff24980563fb4803c39dd7374a63bfe3e5e6e0a002bc968d9eeabd3c019dafc857da250e1180c334ea589d2cdc3a5e2ffd0656bf001d0de6f82c58e58d5bce601bd593cf68378810c58352aeb6f1dabf84c1266f91ae4e61228926449b9dad43bda5ec516213842a8ddda10ebaf878ff70eb5c1e41f3851f5f34e8358b18cef45887ea89a48174c4e4fa0a499f757fc66b516ace216a1d013e24abb479371c7d7e1a5bf03d6f16cfbe634a3f68892704877ea8e27f6b0efdad6f4c4c61ab436324628d0e8d5738764aa091fd7c5956a40026340340a1045aceaa81661214a788f5c0716528556149559a7c201d7aa3c556de2875f5c6387e8000fa6f3fdbdb20be3d77507a8f6dc1fecb709c6e95abef903d39b4081e2c7b1b00bf5fb0c8d17dbc9af0b6900195e0d1fb6e2722f16827d213b57233d94964cc66b3a2595d8e50ad966325113948a86235c46dde9cd28dd8cef42b8302440da72a71e29ed08446c74ac0d84dea10aa59f715ab77c8a6ad0db8d120d0dd1c151ff6822a02a72ac8cbd7bcf7d21dfb55529aad32ec0f0e919b3ce164d806d56428905ef76aeb89bb56abc8180c25869204b270f3d76b14fbd3c49035b373b555ea61873863cb07b3a34226e2fd4e5e65ccd3b4f94570247988a840ec6ef296a8fed327a4193af7ba2bdd8e5a6cf2c7bf9f966fe5a5bca0798f3c42595c0d921f695d7e3e1c1b8ecfd2468acc1667963aeb2c3126be6d27bcf499a2f5c1c1a9d39ca039dace9ec803d717654fe45ecb4a0ae7742604a61269d722d6e0c0b5d46b675680af56f372a29ff951f1d596ba6c6b414a24db8828289c157d4c7bd004c201d64d4e1ba58f81dc34bf009d1630e862d8d80e56b43e2d646477a330031348c55bd4a4ce191d8540ac94780aba6246cdc0ec99e8c8b63e850742422f8b7e5dc78987d8f4d4ecf6705684843e84aed1309d61f29775f80639c67ec7e2de965045d5850d35fb1bdb0f76f74e3aa8f6ecb89284ac84ec6a4407a9201a4f32a2e0b2622adf8f2ace00a15ccb56a24c4d43c4b496bd881554bee4133fd77e83c6806d593101e73d1c786011dbdb467ac613a6617acb3361252e608849409b9640bd549de6f6820408071327db49a0abaebf5ede6e66a87690a995fceda2f3d0439717bae6f0c7f0a6fe43abdd7652de9b5fee05591e0c8fec45c831697b44abf0c274638dfc71b736c01214d3abf661bd640aa695e0e207fdbda4d99f92a72f5c6ce2029ebeb8811acc3cc14cde482bfda49f32bb15dd19b049b702896c036cb38e8631b74922918b8a72d2bddef6010f23eb9e921388579457252d7e9f04d555ed12c5b9be6afa48af0566d6168805e2253a976d6a3c93b5511c8ee6d2d458df617d9ab09faf3c68223dc376d0134c97f2c4d347d426e1b65c92f1a79f80009d9aae03fdcca3c9097e0dd99810611f00814d1852e4a9f5fa1b404c6a9f6e0071de8962e78d8d6376cc2b89c0cc457c1a384f6f230c43a3e84c3850031dc535ea70d09a9e506b8a3ea0826a4a6cc3ccc1b890fd000fc689a4429609bf005d12468d354091dc5dc9911dd1dc95fda9cf7c3b86e6aa1aa1baebc8cb76f80dff5b0b50b765ef479c8412c7ba5ba2dc4ed4cddaa03f77394418309b17c96ecf75070210cf3dc560b5e4faf9bea938fcfa6e6355729d057a10d6a7b4027b228c2bce79e757b7a1a66ab9718c6d0d603d7f8016863bb857f06a91d0456d0bb00f3a0fcccd77235bab68774cb8477436958dc819a6a6d6e421f231ef055e3eac7866739b41ddd2276700106fafa8b3889f4496e756501a0e617172b5248b5c56ac2a7ed2d6ea1d63f45167bd608141501325a661676cb66324447f7d62a0953cd12ed6cfd6f44ed188ce167a3958b45a3cc017a2e3b96eaa3bbb4846918dafa9b51ad28624aa0e3dbf0c1fb60889c3f85d4cd9663254dba050f96f54d54bd88ab5ee56e2fad82fb45425c4c8d5714b1338f514d616a8d81f1ebcc195e53c45a641db1050fd9b70feac3ae20f7d7a3e488d4562f721bb3e087108084f657a23dd5fb8f72f4865ef1e346e1d91d248d7412411e532cfefbf13e8fdec8648b7f93bb7677c6ffc80d87a31026a40e2de32ecc6bef047ee5222c2ad5d8662931da4ff57a8547ca66c00c7839d00adcf4eecb1f0f25a550154c4d73e802854f1be8f15b8db3847375f8cde814ea76155fc2be6770cdc1cd067ff7716285357590b6d8fd58665999e82b02dfb51d39a7022eb7adbac19de3db69a9143d84aafbfcb6d35af655ccc0c4ba7d9358d2c2dfcf6e9684a28649c717cd336e015c82bdab2e96f7fed162fab79f4d7fc1a92d8903b885495ca8428f39579da22d23023335b908c3c3c340784e8b1769ba33e24f2ec96be1b3b9d7d54c5f8cc09e6f1def789286107bb0593ee058d6a598408a3838cc97d791979bd5343b884554b6eba957316088db5fa827abb3b178689864b78c77e15c2972e04cd07ce7760539d864b3deaeb2769b9d045a477c0e231a69a20d389930696846580ff4b504e456d47ebb70d7825dcc2b5bf07f94efcb7a20d772ecda3efce9dc4ef8d171a88df96d624ca05be8431fdb84af438b37e76e267b43b956f04a1004310ed676b6fea51404dca651f8ae8e22e8c32460d34441b72332bffad55f1346347167b22101e1ead22bc7e82da135745c92ffa7e1c28858ed7ee4c4a19dd63804b0fb1a6d402e8a87123fc23af21d273dd5804ecef65aeb023156a3ca70331f5128acdcef99dca7544b608868bc43565294cb4e97c08c318321f5a1de38c3add3475e752b5ac07d9a7044073455a1d2cefc0d5935e9a94f3d680c3e33d51218fda473ce33a308f6b0578028c6c0ed4e40c849d0b4c54aea4d2575fe40ae3203990e03cffb396bc1d9d14c10e4ffce8364b23c634be8db596a09e43c2757cc4910ecadc36f85a79fce7c05eef0765ac835586e45365ee17793c0e4321a292a07ef4db1794a78cb9ae7152a3c718d7a94b223a998b89d0a155e03ca9b5da0a2b758110212949543b7c8c21e76b57c10a595ff33c94fb0a54c016558cd080a6730e4f1c2cbeb792e150956ff47c50effcc35e88f619c3e6679cdd165a1c9d73ca5b381afe1274be9c79ff4408fb647b64d603d1505cd9ce49c64167ef1c6ee11105359b41d3b54ef3c13619a2fc0d4340bb6372a443dbb85175725236fb046303b68bcc5a68afdacdbca95c4ddfa4d283faf8743430c5c41d79553138bb02273034690b6741d6ec8439a1d6ecb5d7d44d57c77c4d1db10596833743cc6f2acb0df713b20103fcec383097e50103804d09e780875f37286af65c3d24a0b2369b13306f9dc11a496b0bc9283e3f8346b635de98833906a32fa3af7aae615d170a8e70d5651b7d97487fc015040aca847973cfa1884b483ccb6aeb5c12164848b57f4141a80f490c70b74e065ac314ed32803a702012eb7ca446ca0ece4fe66a5961d49a715096b637c8e046b6e88f0c91fe050e06f841e12067b7861790277509ad48e934ae46a91d6099a32cd5b21a2249560a2aad145ccc5c1307ab65d4b20f4c1d13db5dfb23b0d49755bd6d42b67496cb78c75cb3e7f6c4b09093bf6a1fe130349dfdf5ef83008f3aa81f36a4d947e92cfebe8d618431dfc93f465324581ebe0d9ee5f66497d5168272a184772d2ed1a2b731b20c3736a6a98adff0f7b3cf43a273d8c5058ac476f947c0e6eecf1a1aa6221187f9a0a04068f98068ce0e4e7d4aa394fc07b1311eba79745450a0914c3d1e2ccd3e6896461d336c6c9bba378baf3bf487831a100ba5623dd28e6a683bf3f36dd725464d4dac0c16c54d9b90dd1ab8e67e9243ebad84d7699b2c1148aab3b7a430a021c2a16b654fd9b75f9e652adc6fa7fffffc364251cd193f36ce43b921153a0265ffa5fb25f658eb11abe2a24e59a4ce69dbe90a6781e5a6fde56b7f90779917ce1d65e04e0e987867a002343cd42c70dd58f76a9dee098e0cacb79da97a8255f4d0e0378e071ffb6bd704e26d950b33b0f2f1d15036700d35003a3479693eafd1001c05902227da3c1fdb81318cdcbf6577132002196aa9840e83c110337fcedf2f2b4638344a0a4c8916ae75182912519b79518eb3d78961f9f173111700f28738f61ae6ffaaa0b6ae7073ba91f908bb1b6d25b959ad68698db08b617aaa459398358a06a668f53a9172f936b9f98fd086f3f8f50c7cf450c520aa5111bdcc98bc70bd4a8e3d5232f2de14c63beddd6eb9a8a98b1317e6c7c1f2142d7223b582bb79dc46f54035c818d3fd9c5b64b0a6117d65614b0ad90fc655645dbf61d44ca9945d4ced10b05802689609c85d80cbedc5a0caebcf2f6860b7c9d8a880bc868e5eee255fbb050617193b13a06dad1d9f164f0378d50eb0cb1ab17165436ea8b390e348cce13f83ed82ff03861261276f3a527f6218ad9b8516fbaa9a680126d5c1f349094f1709d1483cf371281176210589173b9014a166279ce5ec2578221df899ebfa1e5bf140cc215c2ae3eb77f47cf3b18f2f3e790e83abfb1d3ef52a026f039c7d6ad8d78fecfbf2fefc3b3fdf7e1cb8de3ae33044eed28167c712ccc6cf57541b6774b568751170758cc2d6faefe812069cacbea9fc1c2b456ffcc964af236f927cd34c9d79e2ca7bf94b87024169a1391e1094b560677949ff1de814dacecac4661fb6e2226ff2082d766bb5b74f3424611222e44b87bcaa5e10b6d9280b27a90c30de3b90bcb2507ab629a39ca1a828d0de9f3b0454b6dfcec5ae5645be3a0d225954bc798d7404260a623598ad36e9325c89c596b55f07c04814d85437c7fe8b1ff0ae6f2e72de33ade69f12ef7df61cc90cdf61c4e014ae3258f555b836f781f5864d5f104a175e1f473da36dca798ed40e282053c5a7215a809b99bc32e0e6c9d332cccbcd04058cba348813ba64850591588cb4e1a3d0d72004cfa0d073a3c682e05325eaac9ce7e3972a59eaf9ec7ec7a66a8a3d83156257ceeb50443f41deea9fec1cd0f563ff2e1b91aaa62e0ec32c0bd5b9ce56f80f137c77eb83e12f51409a4e066e7c8b5fa13b96171ab197e33030de25c1755ffd7e3483c0acc7b57cc525eb6d6490eaec06d1ff8289b183baf73087898eb1c27a735c6570e9908314b9ed4800a0d4d144a542d79294fc82a2ca8b45e90d81e78a2b0c8b8d631ddf5c15603bc8fac8c7b51610929125f9abc1413d2ea84ae08e05feba74c174dafe0096952cd81a654b18b3ddac7437a1c5efd3f2c9f96f05ca6f28a3606966e787ee712bd3b099c80efc6ee806d95db8c8969e7857af91bd3a62c3b8c0260f77ae78ff5353e13093f3306f412a2091566cd0b0229a169c867ffd215c23275a242b84cc870ffeaac179daf02927d41a87f5da4dab40044b4deba9247fd21ae881a7e7aa5edbb9fca979d39630a31e068d14843c80fc2dd104bfdd128eed43b3d4ffc8bbe434cd045883e82dbb687e7ffbcafb9f3bb748b6a8d5738cb9d376d67a0339d677892e782fa04e3076acad07ac73a2daa6bcb1ce3fec03fc0bbae436b4df408ed462a2fc6a6f221c097fe9f9eb74bbec27d78df763d1b2e48768f7694f64a1c9428f225f2ab06aa63828dda3c6ac27370d64952235cde9ede7db1967bccdb9691cdf4e37eb7c0d5dbb11e636947f3f400174f96a576338efdc4dbe5731a3593e5a7ab21c803b234367cb1e82d90fac82fba3ed5c932c548e28cbde014672e4b27d8d73909260231ad35104cda7e5dc06662bffa3fc07f2614b33ca938442c6fab0d6d483960355272d5bc9da55e6efa5e324ae5bb2ed2c4c6c21329d47e25b15e57621e7c0c0875b6ab70c06013cf4d3a8f73637614e3e344d98afe62ad93ea7b96492693ea68c271d4f554721a2b629a5636f48df539f1237083e1380c57383fbbe50d2c75eafa667eca2d3b3259dc60dc98a8b49222f6a7a62d4db0be663b29a0441abd87b03384dc8475d4c6ef33cbef9c88b41e828f94628003829e2aec306dc3f4b061219e876353447aab78319881eb11d3f6104b3c00ac3b9d5d37cdd1242e2d8636336a3b63e4e375c2d29da975773277cfa161930de74fc02fc99d784d6f5c29baf82db1c60cb388b2eddca8c20eb81cacd6337aeb9c0d8089dd148d268213dcd0cd6dc66d57bc261a9d81adbda2d84838ed94d684308d08e03c9aeb8a9b859940926ced504b4bb85f498cd597d004b3eb14a3a556e6e6e3f84456a01d9707ab5e471488af72bea09f06ee68c8b75b20af95d0bdfc8c75cc62442ea3a286b154c0c6d8a516e3f3e32b57dab7615e033e3c6f22bb0eb018552d0b76297e82187a49034db6beb461038456ad443d23d1887d6f5e8fd3aa7a16b10108f7acc81d2d3813b36ecdf6c474276bc9f11b02c319edd16a9df0964285ca217a6d2372166f78af52b1f745a5169871d1b88e908f30d354b5b122a523c7b72bf3154f6e7b8392189cc0121c909d130e67b53b5c6635c9124e973c70284b1cf359c6ae553cee50533f6ce0942cf3a600b8889fb73e84c521f2fda2a9969697bdf458637486343fa2e8e400309df7cc2cce8e01d35ac91d67ed17083f46bbccbf95368dd63d140acc8eef737e571fabfc49edf1f0e08fb85d5563a29898a17497500d08eea4ab62be9c43a92c95477ffc9b4908df0921c3872a59ddb34ba4d22651f6504cdfbbc1e5721746207787a53aec04ae336e6a6e7996b0256dd325ecfdbfdbe0413820636a222d89d5acf7681c2732fb1ecc4c853b09a12be495860149a81ef76f02a10ce2fa06aaef94ca33bb2e4b52ddd95803a69d262da003f781ab7f5325ebfb0e49d05d1b07f900146d3f193f7917c4e23291a4df439b25ee659e1dd40b960ee3d9b8316ef0152e1793f27a6bf6fe4794014188508fbc5fdbccbbc0f0cdccc25553b40f85ce2f36afd3656eef05854e6dda99ee6bd7bd6b23d3b1420c4df277b273cc7d7b27c8e93f26315f6157468dd8bf0072106ffeedc189f7a8b767157af6d450ea232d8939ffe1aaf2e14550a4798446ecc258e142b183fe8ced4a9b8d88d47cdedebd8890dd4ec02292607960a265165072cd52aa4ffc4e55bbc2651bf9f3a4963c0c74d0ceba657e6d8460f792209d846fcc7332474fd1b5788e41a951aadad97df0950d6a2964e81b3202ec7e733875f424fc12953d9d53f82134470c349a49adc466bf91d1a212474d75373c762e508b7293447decd45135238ca8a4828ef0365f2867c9a5c71de61b502dbe58e614df9585e1d393581af8d6d2bb18d745a3385e1c86814091faa294ad127ab9ec13991b5fc57fc13ab57581d2057ef8ddf31eb948dda63974f391b30a03511ee805b2de9277fcbeeeec26fd7858fcd012cf688d553cc95127e0c8faeda1c7b905d0e9d4be64adb938579cf237aa213a4fe405901fad9bfc5be364bb96e420071121f0de99f5494518e6b7275ac28ee16d22b64df866afecf7482c0f9c289e877b5c36db34059b46e00827654158956578c0ef05b52ec02e54317f6189aa42cb9ee2a9f9491b8894c0e6107b55c0668a7f24e1ece0eb6abf59fe4d79fd0c7b75fe3964e78473592990f140b8ded6ece482e05fa39233919f55c54fe55abf69f75ab9d59d00cdf4be8625ad06ac259010f38156f20f63aaf0e9a6bce1330325601e739777ac52d2a2b1bf3c0cdedfb8480c196f7fde81dbac4c2ef8b7fd00abbab38d60588f07addefed22b9c4796f1a751870585e2450eedb0f40458194077ac03874cf18be0115a2dc089d046b674e1efa632b29e99b0e2b9ada1efc2577039d5c306ee9394168d4944e7b9f37b004e27a3f6832722fafd0f1d7deb7946fda9344284bb02d664d54d49fe9dfe6f436cacd00da1bae6d46f495a44254960d44bed864e612460ade6ff49444c0c80999ea5de4c5045e90d673b1d78beda8a87fa19e41f6604ae9383b1bbf3c887567240bd8dd2c0f2a1428b274aed01e0d60daa42f9c77251d25aad860082ab7b2625233b9ca5cfec396e25fde45046c4ffc0e92ae689c13e5ea8bf1d2e31ba1d55edf784a9bec398d941cf694ae830a024e552e2ad59b00159f5178214c63cde4a31534bdfc27efc3ee9d21f4662c592e1daf9ce69e85a6ae605561e295103c2076a6c4714064c0fd39285ce1d54672589d8f4b826d87d916d9658f1467aa30af329044af9807bbd905eeaf6e3f41175a140c98bb01676724f2d7269e5cbeb41518a0b4df3e9c1e687667fe72863c1812bc42a6a5a77336f8621f102a286f3f8efb68793cd4ad009851fe412d766140b47b66729f100126cf81e38d8ab342a7bc73f02efc529a8634d19f1cd39db6d2ab67b02d41082ae66d8551f5be29fc306e9b1955788ac478b2b0060b61d039140bef90ffa95bcfc6f9e1ce9b8500fb90ece37dd275b466fc8a97e27713683ed4c410076b6551f163193eb6f37ae2aa96bd83206373de3a6fdbcefc0fe5b9357a7a902c93c6f1c07d21119715ffe6749401106a040a7660e93361eafb9bd9503801cf5441f580a651131c46fcff03648d07a0c9bcdfd6d2c7f5eaab9da8c684beb21e18c4fe337a9027b99c7ee780f2ba23c0654edb6ce03c8a0773583c6d39e5bb86c1360744714ee738378c01053726790bfbe2ba8c479ea307bd27d6474327157c33e5fc898e626a175528f361808e8e970ed44af638f5a10b69573897414dec340a6388ec25c27e8390de8d74e2f3bc1557da1fe326f43a9e4d1176315dd4643895a9de7f328c46bcdc3b9abcf2510c199296a2e38cdf47c87387f0f5cf7fc38baa64b2839d3ceab787271a5e08dde7e5bb1676f877e5963874efb3442255550a7af7230494e8947a02e6632893c60713d83799c7de952e1cf840c8e1b2285e7489eb2ec3a46f288ff619ac92d9534ff0a99f896baf82dcc65a0fdb31cebc28da7a02ed01252e40766609b7035943e5e07bebb303c57ac52fbb02f865e267f3dd267fff0c640e965a3b1843a99c0b5455b8299f32fe45a42a84f991feec1b2b5e6c22990323ac518e6ca2b7cc30505bd8ba88b2cbbf48fccde3b53666316d78f142a03024b9a246be44420cb72caaa9f2073cec1ea8125dc544eee61c1b3b80d4ddde65e94f9148562481d918b9853f7b3c429b33ddc3150710c62b38052f423797104a9e1aa9f8b5dd72b9cdb31ce6642fe4ae320021d3beecdbf052ca9b52b930916a6a25074b8b1f14a611dc4f4de6a03a4613a7beab4cb04b5c2954abe9444d488a3680887ca33c9604d23d0f07cd1cee4dcc30ce0bad12b9ed248b49b56f58572b5408d49cfc639c3347d5c634352bfa1cb5c8d97d6e118d6347ce9252cc9219a3bc9e8f362037401cbd2fd680a09483f5bb2d976b3752c4d882f65500655087cfed757b017dd253dcaa3c2a0d6e1eed7e0a60e4d704a7dddbbb166171dd7a75bf99efd5ae529e446244086daaa5f5759dd746679832d0f6e0735277b751e5b0d436c837a0e455340c20be8741e3bd5f7410cf8b5caa7fdcee20b7c70b8f269ed0061f9c0c79f8d17cf04c593de5a1fe2f2503703123711195baf54bf7fd0494d818e6cf0b0dca25b100e6d704989db051386233eaa0ff9ddacada6df1e75d11f45b559494a22fc8ac12172777fef437066c63202f41aaf9efdeecf46486b1ca6f2b35be8efbf83b68e29948cb9f7afad138538ab9ed5cce81692f71f61e5613736ec189549ef7423e881f982adf8c7992bd88d14a9b046a8aac04e633cb31fdb2ea3fcbf009b1df596a096619ea7b717a0b8a8b5bce388d7956da7100779a4e55a0915cad46a838ecbf576d3e7a8bce5e2102732064118bb916abe8f4e37d4a300b40c12064b0f55b408e878b517f9c352416696332202bdd66ee7fe4bad666e0fee5a961b6994729657877deea9a782bf20275aea7e9424bf1823587bd1e1280ebe302297f8baa9854c8d26235f2ac627cc03f2ee10b79217eca3cd68bb1f1a2c797d6378b5ddcda7df9c59fb1599a23bb377f1623eb2265b68028103b3070997fa5f98354e72e631bfdd501888bd85ceec0f1a5968fe79ab82e64b66f18cb09920de2620ef2353e587c6d293f0f967b4ccf2306614c9364006abd333d44ed8056ba36fb90bd42ed54e931ebc3e1a3d317940de2910aae0e847456dea4f54b2a081ed2e510721cf9ce2c3d1bca71789627ae781b21d95503d2a069ecbc8aa69e37ab626577701cee07f5f181c525ad08ea89c3862061ce3093d515a14bf5e0db8f873ef07bb08232ab70229443685e8e9f60339615446d4d36bb963ab563bc14b2fdc70b79fb2b5a1d5eba89d1a8641aa0275fd3bc94a9f8174c792ed64f917398a1e18ddaf58a0a2b47b79419f64c0bb97d01f0e24e9f8cf8239228f275ba318ab77590b22246bb73d648efd6e7bd365ff12daad3e2b685fc6720e08a94763e393bb3616312dd9de12a0cba9d1bf1a4350e40293d70330c3c1ee37a2e93a90f8f3872eb52a722d73a1d74efc8551284e2831e33a1687dc16c50546d4b129babfef18026b55b31aa401c5cd263266940f6be3201836b3dbd291ab2143a1762fbf36d2ab59d4c126746790a25d8f7eddbe80e0491f483a43872f76800327da7081f81c9865b8b420881c61967e0af610a01e7e10f2eebd3e7b2a95f2ace0609ed6989ecb372d2ecb8ca04070629c464422269dab0c9e85757adbc1e0ad16fd8a5ed7aeecd427577e3481fa761f933041d257187b64b3cc18be43ce8c5a65d79613788d63e4f6e9106bf3c16ec748c1fcf822e044d7661ba36d27bf797c3701db8bb52417ec4265c9f796779fa26bd49673b6ba33d8a27a2bfd65905442aee14309bf3b873f91bd566966cfd7b178d6af428817f83a8d98276184d3c947ad9e9f2ff7b31b9ea6c43777c3389af330cd6ba0440d40bc25148418faaee3b476a59518127e742180f6f54872308b113fb8efb3fd9725bf6804e2de983a8b303bfb12cb61cb9aca935a5d7f7fcfba34c99e1ee749240ff7d8e3c0005237db15709bd8a9cd94a19e8100be75b0a62bd3b5e6931a4879e4a87ec134bc8a9a5d026b8c3690bb498e59c8cdbbdce6f93c40db3cc5a6e1a991c872d38f0cc73e48a46372bacf5096aa48a385c6555f43857f962a7fcfd076c7d5f4f1f2ba28450177d4e7ee579d0df34a1e61d779fd1ea2a56accfec46258efd08db6dd3db48a0f1f84bbc67adcd2c89dabc8405a155dc7c1c363827e0490c31d7d2d5d641ccca9943ea65e2ff9a799694c4a833c0e5d8d4bf3ad020414c968b427190782a09ddeaa8ff7f05e5d91abe5ba46797757c1976e15eb41b922b193130787d2b834759c31a6c743c8adf8bad203d17f1b3f57bf882ed97787bffe3fd85337ed7fec0e3502e63b4a0d363c8917ed6e9c218314ae39b549a1236607c81128a09472792dc547c3b8220d44231bc34b9b4e8dfcab2a95129fdac04bd08ecc14dfbb719b8502a93a75f52ddb7fffcd38d52bf7251b2da72a452589ebbea3241a94ef02d729ea061669cbfd770a6ee412f35e475b5e3c35c830c0729be4e1f4d8a81a7d7cc878a4fc7961688f8d8ea38347a1c185536899065f06a0626347ede90442b870aef7757582e867e2cfbdb030b4c82651e9dc1a487ed4cfbb71af593abd88dc22eb87f8a126277ea50fca064c31dbcc694ce6164522aef6c879a9f37c147880a1fa2ff5bd9134ffdd13ec3d06f67e84aa626e29f8048205cbc444d83746c7a45febd75a8922917e5af67862991c1c480d4b811fae2c5b3bb5a3091f4a8dee5a263a4c1f43e2b78c90afc591548af256ed103b8f6ab2a0fbeed54de8023ff2667545000703f84ef71aa45751bb4f7b54b18a40de001a148db54fa72f929aadfdddd0609fd354cc5be0f63f35c55505b9e777eaefcc528dfd6538172eff7a682e2a34875be5cd11c9e16835a15f9fad41262453cc3dc09447c3c358c2094efe3d67026901d894b86e44f8c1469c16a17d8ced130e5372b0474a22fce4db707de3a5acd80f0cde003202694ace322fd6f04be9b6ea60f147653c980c6fd94c814ae174a5812bc64aebc7872b2d076924f6cabc88d590d0f88327ccc0ab5d154134fa5a9f91c7d848cbed62c80d2f1c60bda9c84a894d825d33b9ae3092cd8c47e6bdfdd6a4f5f4bdaa6924f3abe85a5222ed464dfa3f6370a2964796631e3e22988f1145a2ee1870070a50f836a1f564a43e851677236466a68aa2938c37473fb80aa7b64c058dac5129309a17177a3e1e93d765ea343beb0c54c3f25326072d46dde9211904e8df3dd89fb8abdf38cceafc0e84496168c9c678742b475ed22ed4155b5f298d8b9efc3b8596d68210ebc94c6a2cf03c95883dbb0074369b88a4b8efa6943798cc83401738014dcfd4405be3197507f340c0e5c7a4f144bd33f73c6f8e41f037c823937c575aff563ffc9c08850a3a517998fc321ab3041e0991393cfa6e05e809d4bd513b56b56c934a6dd8fc3525bc225a117c85548a42bf466d9ff3aa8323928456f855e2f8fae97d3e283d959e46c546acd00d128f25ecac26013fab2b483a4f4a638a01e5d95c4dbed0f71bdf38c1db6e59502f25c4ef88974c47c6edf51e239e8f7a796c5dae0586fda45cc522789a6b2fbcb26d5595b10077975c9d56a370e9abc7fe22d71e3a1599a3ee4665976fb1285af92cc2735c42a674b08d55145b1c42e7c423b6984def117a2555c47cd452a4034b673f1f3aa8ef604975c983969da7f2d9085de68686ec06de55849cad33b3645486abb4a6be3901c977ad7b3263978dcc59c837039cd3cbbbec62de8f89f365a1ba28e0bd6bd9765a26ddc03055124394c0cf964723113cf0a2e89d21807e37d10751a8813a2644e6bbdd84cb408d289bb02b61f6dcfc8f0e0a67abddc0f566d92c61fc54489e842e2f2324f6a8a48f3c6ee42a94d09bd1f6307f55cc05c85890c1838f78955a890065208333f33d17d525e53bf2d3b9180fdea348e584d2d032655710b7db051e56d7126a5a576bd3c5bd6d6fa16dcf561cadb2fb3e168d283e8bdac4ee4db8469f2cac0e97cd30225fba0064c9fd96c72345c7bc5d448e14fd6a91600812a6d0ca3a9553f331bfa71333af7d250ed375469cd57935392f054b28ac9d5ec13c431d15365b5243b8ed5155956dbcb293705ab7530a7483d1f3bd5fdd725fc55bf7eb331d9b4ebbb4e37742fb5981745168123da2d61527f8c336bcac15b0454e965db6a9bb332c0f584d7fcfe19f3c24933f1fbdf33e44ad409186d2353e09aab7fa64819832a2dcffcebeaf6ad26d16498ab4417d7128bbee59a9a409a40044f1948458148a7ea004ba210d66869866faa1251d682c1477c23123138b00681cbd75d4a0e996bc8fc86ecb4f1227a35db4dc5b07bfa89d68fc6b06cb41badb0291cd2fac39a4d59068824c32fb17a9f00aca470fce5188dc37b1950a4881c52ad7fa9c0692d560a9377ea96d2d63355e27d8f512d3ee1b0809e0364cea69f3d36e04d554ac53716b5669b4bae52c73bb5b594dfeda911d17c02fa554ff6244f19db01ab9106a02ecba4f675a63e664d900ef4f430977cd3769399c449482998cf6584fc924383afd66c90a465518f46da023324c6fc5dc0c8929df4fb0fac4df4c52b5a86a0810c77409cc4688fada45513e8e3ced72e1e02e2be68198aa2c15a895ff622e1f2f15e983bf552d2b85dd0372bc1be12c88f3cb2c39e3a630b2bcc3c2c9092f4d51318401f686d40afef670d80e565caf56ab7b398ac3380f02dedbfd87c5cc623734a3854e38a6a6eb085dc301312fd9eefc374433bc9c0bb6f299a1584439a3d8eedb742d76354fc7f2cafe3dd2312fa8e6bfc7d12f959c6fa7aa55403f0bb7278e9f5b7a893e7004fcc520f98c2c711cab41ad0152d98738339f650255615e436f4bd9cc9ec2e351f9cd730c4dabae8a4db325121d4c51c39aff010509fd32df5cb324599b9aa7fd75dcd0fea500452f1a52cd2403d7699d6d80ae5e0273087aa49708e358e5af159a9f6634553e6e8ec3a2cfe67808c3763be516c25c2cb28c8b511df6d12b78209999fb797e3044c34fbed605ba121937a0bf34efcc7f5a25dc3bb7923500f7cb8f49ff53a86fccd71b746164e9912db6685589cae685c3fe1f615725bedc7e818ff5cccba3ce95b7f5b52b9e772ded20151e5eb2d442e514f930bc315d24384f80c9c763a60c5e1c73833667e0b3885cf69b5799082babb701e23822d4bf21438fe785218e1378a1aeb61ed9330fccce4f359fd0d3ce326760c126516544e6d210f36b48bc244d0ba07f327198fa895bbd81282cc498b49f0d53999c3ce1a03eced7e7d82232312317414b535936bd04830a2f33aa764fc442660b38f745e2c5a71d5a1d3777617995d10c78c32047d3269f335c6e2289eab63ea30a91d54f0eac6464a67a0cc26c358130d8dcd5975be929668253d07dc0c0ce773004ba4de1b1ddbaae6e7cc2c8e721a00c410cad53301705616b820823ea8bbec59e8738953919e3bb547825230c1112ffd5b9de562fefa43dd750617478681801c68f6073a1f9404686f8559d60fe012de2e67bf5dc494d7cf331cc3f80c8f25ef4e34379cff2aa99ad15ffad6ebf6e2eefc6d737848026d36f67db91d232cc67ba4424735eb1bdd924043ef0f479cbf44a72c557f1988cc6e63001d1ece5adf34a907e371bedba0d1f22cd7df9c048111b59782fa71e17501949a61b0b13733d4b125f9f6ac2378e4a685c9cade15822978211d3311eff6b8c5ebaeda7d6f7538ba9093bf179bf91361e0afec0dc3685ad3ca1a3662606f2c068e463a033fd00809f38c49f11510cdd4d65ea4f33f124f07d2bb65022979a8a3d15895ca055cb3c0674f9106a5ee93087c0ba1729baa9b001946e0d283e3346b50bcdb966cc25400b96b1243e5dda00e1ee0c38a268767962d5d80173a7a912413f4eb9690a6450c4cd1ec7b0c8e0c00045b1fe514f2d7361e84e7f9266e85f8493cdcf5e81e49d68cd97efbc5be9bbadde134098defe4c072d9cff3cb2d68c564e0ada78228d1e3ff398e3ee046352bd7d132622c6a36195f82d7462acc83037d5e72f3a9feb56f067f5cdd160136882ed94b02956e3db8ccab78809efc7f8daf55f620c3c53b3695535b61b72e408578e38c088f52fc8175904349299ae1d46ddcd88f1c1d9600c5fb3cf1c34760606d447ba6ce6ed8bfb31a1d2aa85eb01b1931f06e1522c152b04bb6c9c3f5f2e767c6522d1832424bbb84fabd8cb9ffeb0e4ad5dbf9e75d19f07eda53db0760b05e58daac6552e4796d01bcd88dd2126b847c7b524fc39ab92f571b41fbe88cf80db04ae3390e0e185725c8c9d6d13f91d20c385e0f634ee721ea5ffa385bffe4e216122c0cbefa0b25d93677d3ab45cb82111dcf628638fdce9b526771ecc88f6f8ad81d92d6cd8e7dac653334b118a81aaceecd3ad1a5d9afd883f0518c685d319047364e686b23e1a1463a916dc868c6e0c07b7f50693b4259dc61a5f4a4f3002cecc3335d029501ced6428afa4eae2994ff0e6c330728f053f4a7da3d6c9c865de605838516e7214be1b7b82690a14b95da97864375dc22a9ac8b4981883159013a2a604df1965fe53cf674d9869358e264b2fea17ad3a19ada9cf0e32684049337a983bfe19e1d75ce26d78e1a7570949a67920c47ef74566ab05eebf8774344b695adae4d23d292b741547bd28d5e37fcad55342ff3f9e1764a447d5f7c4e9f5e965455e53f80037d99c95d5767010d098a022361c0ec6c8685b713828a508edc9222de1b1b8950aa0bddd3aff457c20b83a8a8d47433205ed69fd02e6475d8bb0c81a784bb070882b8968cd86ab61217cbbfea56e39a7c1a8ed89697e893b14a5302a368d2a25045e6eff0944ff3c0f9776a738fc1737b035aaf6c9510c00a008fb30f57f0f11628ac4850273d4607eceed181d2c67e3c3fcf7f777c9ff6837cf181727b0c4ceb60d69dc860310e4dd1a21f366b3703fba23a811f126f9d3a65988d9fbbfd302f3e82e568d5b99612905123ca5d1bbb191b5836cbec4b8ae53c38a2644b7e16abd5e3fab1be5c6492e59ed49a9685550e7375d859d6e6495aeb8b42e2fc692324eed569959ad1b0f95f73a8368c318d4a3ef1778e840737afd6c4fae09ee82beb55df64e3d8f11a569495cfad86a7f2388b403c1a571ccbf14af50682813e956096728a3242190ef2fdcd8985b9172f4c77064cf91fc3c5aed77ef76949bf463bdde1eccad5ea2fb4ca6c5c70d5fb93e5f4637c7b4acceb2264c010165ee5bf0afa379d05e22c20164f508103a570528b0c64f1f4a15d43676e489e71e3cc1db9991d21e3281cfc5841f0abb368a0f41e50fdb022f8ac90598d55ef434af72660ccbbb1fe3c15fc3b7b32705e809ff44a2715db46c7fccac8d662d92024e4502d3eb4f20816340b8cc2b76ce79bc83960882c42cc9dff815c69382f4eece5cce1e7b97a1cf657d6541cd6aa6b08c385b002fb7178262ec2bac872a3c674922e308d81bce7dba067e82620e80735c296bf2b0a761417fba10635bc7046b828bf6c93e7f224913a67364a3fa34095447985104ed8729045311dee4c3ae4a20f468fcf504088967ba39ce6b698567b426bc82ac5f5d10b2029070b695d595ef58320d0b9213cd13957ea3f1dc4857e0e6f416bb75f6ffb96a2d541c313bf1ec6efa79296e9c2e478e84af12e7246b997f8a072710a7724aa9035b5d03d08b59e58cf1c04831aabdd4ef38653b4cbbb3231ba3b405197717b521f7883a54d98e2d5e2fe84fbf86a063bebc90df721890fb8ccc515bd29cb7118447b6868c7f11236973674686ada6ac0c0581b62c2bb76c1a1e7231fb42a3dfaf1fe0718753fbc537878b68e13f830a008b6a12e9515f4989a3110ea36031f69ebc9aab9a45b2469eb73dfba42123e55ed7804c55375e544fea8619a6b7be33038ab9fd72b083eb8679a952d0cdf94c7d1d785e2669477382a0efc17c23f0c5ef8b3605b38a0c82de2d240386d61d5c8a84314f0d8cc78bf657dbfc2e423417784a55c5f77f9d78c0a252ceadab549dbd45fa4a129428b88ca6f780a187bf6640718828be97dfbed566ee3ccec602e01d2356a003238ad45195517a732e89e625c97a0ab67b8980c2041fc0a267e2c59b0dc84b3a743a29c4a84249154995fdf9e04dfde1b28530cc30c25ffb5bdfca0daeebd3b2ed582e88ff74f5b8dbdb21e71c48647b9d5b319274fce0ea70da7e9048c9cd1a6a5c9ff0c95cd18e1efc2b68707bca2471cb4895b2c5b960a35e87444f944e2fec0fca07c252de4a8f18601270a3c7290887e335ae3ef8aa996e07e67e45d2155f3f379937a0562cad2049031574e92345479eb555777d6246bdc3cf5be25bc6851fac4265274346b4e1a40179c02ce07ba9ffae4d940f6fe963fb79baffc9d87c47aaadd781de79c3e44c6c8c9dfe833378127ac439317a916b1f469f85d18f379a28c4e2a15016be4187724bbcbddfec82f55b6cb0c3bccca3644fc2bff9a52bf618eec2af5de9cc592f50b917e6e1e29ab99f31b49958bf58a73ada881f33d75bf966d67e8f55891fa5435c2b22eea3b06114910f941168ff878e2f9ad2290c85535678c82ce1ddbdc40422d38e2c3dcc90ccd5d5a4d3998d780010922aee474de696e777aa1ddf8a6848780a2db5dc3a2510619daa3193eef1c3a44a248002df5bb0b210568015924cf2fb9ca67eb7cd3e0c7f36dd9366a4fadb7a7fa43424370c6e597a215fd985b1d973aadb4f35ffb7c17f7ff9dabc7bf9dfad5cdc936af3c8c058d20b20925d4cde007808369a93a0acee45e9ade5063d62f24f8c0e2968b6cf970f4857b86a3d71f50ffdb138c1a2fe0e5e33fe2b7662aac0ad47d54cb149d900dbf4b9f6c095263ac68912b8b86ab10d325785c9c80979d0d463c598d4a267a46ddf5d813c357f553c7e1d1a8b4cc8c12e6943f541de5e2efbb26c925cc91f00a6a9ddd063ddd73e0bd09146b13ab8faa94643ff4245e353f586b1ccb6e58d679782e62b7f02860d8dcb47cf1ff5d5a898a8f62940c4590f073c7cc4a80f88f105e77a6f580677e31ac93b202806ce17017d10f79f78b68ea155003e1721fac655a94891fc420e805599d71e93341b5df9456651bf57485e0a85e9108a61e1017c7fbc252f0ff9432542b5e9e857d698ba34dbf7515308a00149af044b3be5dc40480c011906eea6729be4e2e0c7495954f2016badb3c27969566ee9ad1f8fcf2940a92b381944e92dc6d4a3562acb79c376c5b33296f05432d294bb005e71a02ad03aecaca861e112438a1044ae5080dd8633275a1caaf7263e74d35c55b95f086d01795b37d52b0b65cafb1c557969126884646c5779ca3f5ad7a90c2741d732f989b360f08f387bf6e2d683458b50a2c54d5358bfda9774abcedc1dbd1e246c7177803caad0ccdfb642ea67093c0260d6da30d248922c268d6f96ae25e8a315e6655fa11d31b654d76792c9f77381b37bc0625ce6d10e16c3b72791f152b7691ec7f68586e4bfbf25fb8f595c80d0861c5d32eea2886c48d58e8918ff3f89280510d25821ff903666b4d4239a824d8bf4be44fea1495cbac3f6dc2d9b22bc20c0fff928f0fc85e5c1ac5c6c8a960a0083450c78c86606eb24f59c40e15d5c8b74a775b085faacccf2ebccccdfbf0dbffaea4b97601329d4988035e1cac7cacd8aa892e08077cc1a0cc2d8b79b99a9b96389c8ffe5389e20883fec148774dd3e20d80704c25f9202470ee6465968db9d90ecbc40f41697ef05d19c610da1501bc19beebb1164be79e94244719802724ef301efa47cffdb3228b6f894909789675e0b787ed204d5bd13f1bec1dba1b363fd3ac0ac8781712f86e224dc2c347653109de07e747b60a4a0d53b0b46e88f539d1f874f0511e6fb841a821973dd97fd7eb7d17ded5be8511e3e5cbe9606a75f40d1a6a53d82b1d6edef3b9b8bd4fd5565309386b156662937d97b601ebcd3c4ee5fbb75001dd5dab6ef9b303fe0c5ef6947cf7ff87c05c03b6e22a5e3f95a2c5c9536cf6a7863ac466e5b53269c192884f778740f79ca850c2ebf1b54a3e80a733a7f6fd6b284062201ff1cb65ef72d10a87d5588ef24e07da5ea33fb16399e687506284049d37bdc95246735f08537e9eb4531e61f4fcaad1f3015be5d77f92e58117e4fa79a0e2d878ff49e9dcc7449c6307f7048c39fec96372d1fe2195dbd76157ebcfb67966861afe67cd364606405253261be2430f57842976bd455a717499b0f2db87816a378171ddccabe2ffc0193ef65dc6fc6f40fb6b9caf6e2f3996fd312d3180df1932a009baf8c42517e666d89963f188fb7ae16b60fb56ff7141b9f6bbc4d32a6c2f9c43eba54a7e303de6471e6db92444dada8eae307097cd4eb4c2f2ba96654e697352e95ca754c6c87615fec6b47be55795efca7cbe63a8e8263dbd997fcb8abee5cbc5ab37400d669daa8295b0cd6f2e9815f6b6779db41c4b600c6fa1e54b2f6615b1c05c382b6e9a005a985629b8a9656eb4b45454c05848740179c5043f1b456f719a5e323a5384a997914a7eb861ab7d0af234f73b40ccff0c2cdfb3900feeb5c29eae358b8f4a2ef15ed92092bc9dc831f21f3ed348740744b322fb2a5f61f1ea170d703f0da6381a09c2947fad7bda92fc04320652a4c44091e60fce15afa334b7c671c7fb4b4e41186d118904bb6d35e9bcfdaaad12ee381cfe4d6ae28101687b6ee9be7c379ab0f285e0badb065205f92a1d542ac98fe428e89954a8e6b6a11b7b2dfd58a5832f3c6bd01c8858078edfd6af16e70b0ceed453bd80a31627e7ad511440772a1fe9ca3e1c4e654d2561e7e50ec300a27a25249052e385c1a518af3df40eb8148d4eaa0a29890ca9ee8a86739cdab32721a62bd259b2613d65f1ce899dc1bc832abcc09b345986319e3d0b34d02238e387520f7484581e3d8e65ddf0f774afe1570fc4886c0b54db565fc33c3a2758a1b251a7314da1903446f49a67f6b6aa57221330a8139ecf73050542c26696128f33a4d6a29de22640f7e57a61ff058f79d305ebbf510d3c0be60cc0dbdb26c77632d56a2933aeb6e9a12cc01a1fbbd0d7049ad2d3195386721c49c605b19ae42f0d6ebda523864593e2d496966706d2fa9ee9ca0f770acdf62e52a68f1d539841b7d0e981da48dd6f64265b47431382ef9c3a1ef154055ee09056314b31b27700b32a2b3d4d42eb4357b4f22fc43c3d85c3b83052449ec58b0bcfdd76ca05ae8c7e1b6394493f05c94aee3a41178e09d58d095da736fe923da7e083502c5c311793b807b7fd63d6f0858e09a4acf53be194ef751469511d003eea4713ae60dd53c89c7d965c5d3c21b80045442da8247f87f2c4a1baad02e0b14ef1b4e5fea1223645d6c832d60f439fa01ec1d8818f993f53547825ca8dd65216baa6318cdd66e0432246bf32235a0b5f2bcf7722aa554e86b663c3c175048f190139d66cd89cad4843d1b2474580cc2ade7e0b5285ba891d778dbefbd5e90a4e142472ecefba288bb3919818614dd3cbbe7c478091031da3b93094b25af9562268b78ba7f205bc64d490ed844344fb6e89f134ebf945ccc45e66c3d1f6c54d645ac82b11d60069125d3b5db7c18a90b82f5b66654863d5c4bf7b619a22ea6bb4d3515b09d1f13826d8d81f9db096d09cbbe362e53e5d720406f55ef7ab5265280dd30675843b695a246ad2afd51fbf5e79e00f584624c203118aff46c6d3c974aefb38a46f7c0aa9a845639b863f886a092b0493ed7746a7067de28ebd62800ef617b48e25326eb49b828a2c7e4a1507730737c88f22fc67f9a6b5b2e244803eb3ab4cb202f9eebb553be847d2b4ad7a300a56fe53a002c9947c71511d2dbe266663b746d8c18ebcc8dd11e900049eade04ba49c811eeca8bb0891f3e759e67a17fbf7b94e904fefb8142ae00cd4b0c14801001cbec8df8be8362dcbf57aeb0ef9daf896493d9f4f4825e9a71e8c7c2c119b9710435fb3f99eaadc518b007f6fa80248381e907a6b26da2e0f6c8144b81d6825ad9d827ef213a55f82f184c96994da50247c0d741fa18a61010ad7b56afe2a8e60fbf0825cd5df93d3ae271622e16d79a5b5d01731b514e568e9c7a065e14b303c5fc74743921ff9a6629e79c77f79ba764388f1d6525d5c183822e3dc0e848dc801279c3b1a55903c15be7e098c0bb85cbd08d85d705d95d55c0d7837027ee617337a45dd91a4f96f559a1a58cc0e05f301a97e781188f7b403268b3c8983bf9528a8076f503d601306c0e1589aa52ed5d93a6e454a8a55601ef3f3e447c60a723808736dc493194e47ea4f85f3908c4bf88650cb7a36ac5550156b46bd4f3905f97f527f19700b2613189f1e699bbc94cd799a59c2e4b685f43658251233ee4fd9c820f61b9c1b2c2977a261c8bbebb206922a968cee0db3f125a2bc65b1683376ba54b02f7ba833bf462d3a7dada65de871f8f3725f326a2f83da86f1094f9cae51511b652a62978a6da4e7756fe7582c523e595c6a62fdc676dfcf443d3ffcf74deff8f351b570b36a807e94ba23c24abf29dfc9e22020def37d99dd9c4e0a92fcc989f73240b30f5f71a5979646fb69418676898f17ab66cbaccbc8f0eb031c5383e85fd413196adee0f5b923d20df150aaa6f05443ccd7e5751a5ecbb61739b92421eb2a53db45ccb97753954ee4fbeb045c18116ff6de1eb0c99cced790b9b21fb5c2b5fcf1420eb6bb455f572a29284cb8b0c276234061e569009ec44c5c421926ad3b988bb8e8f2b59e7b223ba6acaa6056529353a8b1ebde7ae9f014021d4fcd2766d5bea136bfdfe0fca6197f262485db5e41b67f3fb827d5948fe73a6b5466b88ca34a957b59e9d80c6089e032bf84f3aa1c5254660007802b6087386e9c8aa2bcb7dbc3be4ad75980c161dba7a172fcdd4a595a6be45d294d827b1c7b73e3dc7f70ef612a8926aa18ea0f3b4ed63f66ca9216f1ba9a440c081eb2b3b012b09822e74b0ba3ab2c9d020c682a33aa04f7e5ecfd3feb72df45ee8d59e99be811118f6950e523088ef1849bb3ce7108ef28ad6ca667aa39a04dc96d20e1618baf098dd22d1642518bf0993759ec3811d18a507b050dc1a09605e298394ddc1363f776195b0099150d0eda91967728208433bd657208a3dd70cfa81483af5a9dda2549a7e6edeb7193db93b1fccd2ac92574b3ebc37aea193c1e9556ed3734c424a90a3dcb9bd6596e44b4ce213dc2b8bc89db1bb6f0ef4292fd17dd8e1d6e05272240b420a56bc91b7ff5dae5bfedcd0dacd3497ff1532523941f1fc4eb15ab6e809ffff81ecaddb95cfcf926c25d1156b7a4b0522048d12b61f9924f338188d333e7a8b36f4f86d1a8f95445eff74b42f96d33927c15ce4fdeb573dd6a0c528b717b84fd0028e1edca4134eb8869b13a93aba3429cee64d4a27e1bf99071a126a064f81f76c2aaed71152c3e04f991ce69eb7f9750911d24760530de3fab04a71350480f5aaa297757c402c540babcd7830217de832a6656d3f3c5805cda7313eb2192df7a70d6643b63ffcdfbff1aa65a6d5628f6d827f09189accdaab4c996e05bab38d7aa290bddcc4057d79963e2f37ddda7b77e031ad40c9be5a9637f0d238fc2e1766c3919fe7a3bd094be7f1815a74191f5cf3c963de1a3bf0ed5e9ed7f8a88ef8fc410af12e710b47588ebcc5eea0ecf28c70b1ae92364b3b47db69c1adb59abcf746d5cfcf71158c3326b6909946e3654e522e543ae5532435f07a4e8f472c657a69c6bdef03596a9fea565c1b171c37a46895a7faf23e1eb9bccd40157ac9d2d3565aa37a0b9179184ee00a1ede7adffdb8f95cce1e362a69d3bb1eba33112cc45dda86bc469094f2036637abdf5dc75eb7194e90c8617a8af99d8a5d11203090ac406f28f3cc8069a4f6a0fca9332681641ac23be24c89b831a78a75a28079eeb543430f63342185d8b1dd61c893df2e4de0c98ef320afd5b764b3495aefe6c6d56fbf7764372fbcfc70b55e132beebbeaabfd714d2ac2f62d8d377d83a2a44b83e6494deea1b0849f92994f38b5199a9c49bf2b8ca0104a429317c00a3b7bcf3ec9ba0012b1585840c7a54c2470287f17899bded2940ed9e379c1d50229b8f429dcce2c04aebf7b13b4aa0ea87421401108b3232b25d95af11b04493b1d87e368c3d95ad8324bedae4d4d787fdb83e5aab849cd20e0ace129bb1a56ffef2af83b614face256645b430c887ae565fe35b6eb4803703ea5676a19c8fd4dbaf93f43d6ee9884cb9c561a0d7466d3515d0124630b95c447662ec340a91311c0fa4c39d6e101c6ecfd8f90c8dd0176f6f4ffcbfb2c1ba65aa818f95dbebe08254fff1c6248bf2a7e5938278be770fe7055bed449aa523095dfbff9ee0e1f35a28188d1512b7c8ec5303c18b44b93cdc122b2157de6ede3710ca97c64e89443a8ab19fcee1553f2058fc2d54e7ba071fe4ac0621937de9812476a88af62088938eb2f64bfb4b684023b57c9bb4e49c50a90aafabc14edcde3fce26518cbc84dafb71bec164f72ed24de18f5695b317ec33e51906a8ff4343552ce0046253d5d15b1b0b46d25a30f9a91f7c069129957214e98708be32e1ab78647301c20102b09d796ebb788fcad7c89dce4d36cfea5517666f6d2b2dff937dc017b9c4c35302a80d37ed1515bbaa926db491b4f86c2a3541ea231a203c751c76f4ff43e4920061563287d462f2e73db04c6d88865801bbadb70f6cd9cec78bb6cc7d57db75ce6e751d1fd81625c45c12e86630d1b044e32d7ca2840a2b6354e5fb17229993bb92d949ddd13c8a916d500d86d14040538d587df353c3e343e86132f55e20e68c607227e6472f6fb38ef832fb5663d6b34ddb58ecf4277b364afdde1b9b3288d6fbf7feea76143dd9102f2513f3e13e1eac86b659d4307790289eeffd5dc6cf96c18e4b14ff71241c6a5187f7f71ad697700e2ffc8e0c718a91dd190ed59151d55e3cf46b74aa0dff9e7ccd9a594f4f73000aad44113800a4c31b209950ec9b410a573af324da82210312748765a1a02dee4c6348cf895bc325c36f31e5f458cbb6b4a20be37f11ea83b2484f60adad6be572b34c19cd38cd3718b9e67fa9c3dd611d809d97da180686f18e51934adc0ba5ed5f4b373b44f80b5ad7f69cea947835ccd5a62559068eb22af64121366f5722e60b84782af9d222b699f80280b7ba95de6bb3e9d72759d3c0412266c74e7a0ff66d542af7e1105569ae58d3e4d3a864c51d8c80c5a9e95971d93ada65efcc44023733fdb9d77a72b14048b3396525208058bbdd435c6f360469cf2dea40711257abfe97a107f87de78d064b6a26b56e1154d90ef0dad840fb85cc87e49f97924dc4532db1877ad7867a6ec894072ce7883ffeced755befbbf3506bcf15f7304b35090d6230740b19e653048d611f79fdc97c5366c464d6374b870bf70d6385a78c1c29c3be361f2c9332918500c7c21fde7d4d61b0c8ea09475789c986e6b4cff7badecbf55ce214d53172b4804fa35a2133021946c3d31b27833cbcb6c3389365ba17b483954f50c43e1d9566ea90b4b09fe156d5d51e4e84972f0d17a497d9092676f371d61bc0cb8a16b4f3772b67dac082eebc630615bb905d6fa92f3d91d5b7d0d12526261744843eb79dc4fd50526b2bddf7c4e585e207e9c16b5507266b6673ac1c2f31daf076665e34497bafcce16c524a398d630b597057f741b48dd377c82aae1158c45efa9f473f1c905a7b4733bacae23040fdf38aefced74812f2b4023a3782831eef9c5e28e08c8144e509680ca7790951dd640741db3d0d84d599d90945685e401533a15ee575b748393a613eef3b5d6fde6e48ee97e5a41d346883ba35afcf1f671c613c6c46c5e66a5a9a954701146536f17dc2cee03a82c7ebe942b0bba10a3def5a29f06b89142f1f9d4ac61cfd35c14b5dffa0867e5e88d1802f44ace4309cc7d67fe7de8e6d15ce8aee0324038d904af79869d87d2fc89206e2afe49be1115c47a6db0c2e8eed3dce12e6f566dae0c638b2aac436eb1f8f1425be5c6d4e8a844f371443216aff1469e1b9c996a4529f498f0a4b6d53e9e2340aa6040074a53a1455395f44f59b49ce8edd6f5416904ff4c4e9003eac2e8d4f399f7820495744cc3bf1e774239da273c13f3bc5056c06e2e61b79b337047828c9148679a759940303dc72543f33f99f6836341dd4823cf50f447b8fe3d931b5ee9071479728ba0db6a18428a915d58cacd66febcc0a7409739bcb54aca5241805eb11b10da17cf4e7ded908be585aa486a8765556381aea3aa21cf6dec83f910ef58e3f56ade937e37bf52badca048b92a19bc7ce833f777967c9816b6082bf62ba4be7f8c091869331ec8dacefcbbc6480ec0943d49b05974e214d1e540317b7e0c52d02083949a5301bd27efea2c55637abe20b799cd8759068858190cc2f18c122f898b89716992645bc2ec9026d33fe27d0113e95fee565bb801011add4e8794cf8690667c65da782535d5854e7ec66f8e43765c16b1ae63394bbd239611d7f0db6c599b8a5823c41e67da4f74d90f777034b815cb9003b8e8fdb6da24d2c5ac42d6ca31dc33a0d2ab667b9d55fbf663f9c5189866b8d0f728cfab9cc9280682ee3ae08dcffc85f646e5c71429078c8f47d7c8a2977764752558a8f909d5507d5bf145c92d0d029050ec33c7183d65f13d412e95fdb21b4149517f2c33b0b9a60f05e38b529e06bbdf9d2eddee46143420b6c9b4f7f77a306f56f29248c86f720dbcbd81373874e9bccf228116f5fc13070a3221ca191b1238480660f3d8536d1b7f706ab04b5cc5f356e827740c5df42881629b7a9023cda2b81a6451026694bdbd6209250ae970cae5ece3c35150343ed27f3224dcbf9fdec6ccf78cfeade47763c2b43bef26947615aa726b2a2e5463743d2bf6c35a4a6a58c6bf730ae574a90159068027c2b9373680cf629d405ff6277a41e634264bb6d6805e21d91b209f7a359eaf25ed1af52cf0c453ab396adebaa5562b9c518c19c14af04dc29ba220a3185196f8511a4b39fd86b654f9d3abb8d552dc6dec64ba1b2affe16f461398222d83f9de08a649d24e18344bc4d52bdc6c74a46cd74ed3d42a6cd2ef6d7eff906cd3dd03c3b296007467835b6e09b56fa190044b2a032051790c1fc77c3a4bde480a56a4a2ca9fd81182993bb75d4f2b37c5552e298b628d5280378d1d388bdb7008c7693a9d6f4ebebedf8aa413d2972b77d216368db4783eed04f9c46aa8d7dedf0183a3e9051ca1dd46fef265bcb84f23ff5b4bfa140bd7f6e80eb328207e716e1782987da814355921b2637a561149452499ac1e6acfc297a4460b8860bdf644be6e97b7e24aa61d5a9f01c7d65c69dab4c73b28a359597cfbb653e0e79c39d3c7cc964ef14ffbbec940da0eeec8182cd9fb4f5fc70e0843e408c1db8172601f3bc51ac0804aa53d21ac3be0fce3e4fc7948364d33b2b83e04aa9ede2b0d4fc3dc987ee6a754cff3e4a88de31ca9595ce2aa6c4b924a412939b1e89374d9d0ca196919fb7042cf8ee87eb78d29fd8341b8a783671eced98251d61068fb3d687105248cefa2557ced34123edbb3acd12d74170a27c585353a4905e93c5d46bed23ba900fa929535ed5eb47902fc19cac2c7f7116e1a2fd3ab90935b758392c90f3667de44ee9a23662dcf9e781f5ba77628fa1c9792325b218f8cf8261f8050480316e55bbfee04f01946f66ac49def1404cf568161644999b787cb2f329dd7e5c8dc97b9fc1009c49e12ac1f47d2cd718a03cbede8b3e726f228997ea84bb88102047e8211f9453fc390ec3e391d510efe6bffe37651a28540b6638e1cffed0eda8063be5b9a60f5daf5a7ef95c4ca8d28c8debf70f3360074b366fb4609ddb54dbb62cf6553f9c5ac275b93b3e027c8bd0c2073168de86dd30cf51ff08a537ab9ccf681e60e79c3fcfa206988cbf58dd8e36eb452a819f6d599114ed587646627ed070217ae91ce0a9e4c9d8ea2907e2614b9b0ee5e0e0c9b9fdee757bced5b52f200d5f485ee6f2e72915dfa8644169366f6bdb114add2639cd2855d1342cd45aa87e54a6b0dc8609015eeea765edb2ee3af9ff1dec89252875bb6efcd69d3fb004a6b9c8b4cdb0c78150d4d97b459c5c304a26bb64e2770e92e1c3445dd326ddc733fc44054d5f2509d920a0d313fbce105b7510a70c962ae166b69ee7df95b98db8218c44882cb37af83882b77503a548ff081efd4aafd73413a50311a64416024b6f2c4ab643b6dc68a8d0639d361e6a46f97c79ea21297320790ac4fbc60152da73a8e252249bc4abd871ee3182947191362409b2343a7cafa212b83190feee74c08f8992317609deeec38364b5aff541894eb5139034d2c273de8b9b1e467047a08e294aa1e822215fc38abde7723f77dfaade2995f31626968506ec7d4d6fbc1c4cd878a72cb5bb92f4d0eccc9466a1d08466c48190de644deefafb14cedf9c2455adbf95611955d9aca2e4ddea359d0d0557ce75a6f79be1c0b41c56c6fac124e5c0ebf5a10f465107376611b8a2277693f4abefb359c1fcbb7b3ae8f6f06ece8576c413399cc11bda0a6d38fea9a84df57eb0e67f3cf4716708ee580725a7d0ce0e13a2ce6252a9e2bddce1626cd160d3d877d2e9ce99c8cf4f8ec0c3f9f3455e56f1b67ea0788c6e6e480c35a549c40b1850a4f2fea0543e0d926dd5d3a876b61986bce77c777bc32cf9f0841b9dd663e280369ef9a916e8b0ef60fcac207d604cfb5579f58c33562d12dbcd29a53a53e9707bd687483be3c1a2a3c6e132e85ef58d8d20f09d2bffc72661feba7b1e0dc8c17be6a945f664e069a0ae9bc6da4ecbd47b1ba5af812febccae434eecf1bd58c3bf06092ee95f494bcd4373fbec43c684d8d8cf159b200e65d53ebb693beac8ffdc39d7de7e5e4d9d1f53410fcc55c5022014d2a1aea60cdf734d3247dd3c2a35447495d67e3ffe8a8640bd0d02baa734cc837b755ee656828deb339cd37a2779596aa1cdf2b064fc30286701072c043be7495d07e3fdd618a1a48fb294ed5c88351905ed98d60c4c454132902383c8f79bb2e4c0ae80a7cd66003ce72cc26f58f3405258fc494ad175dbdc305bcc891274c8281c24ac8098d98f66a87ae40acb1c83e885dadb15bfc119849deff2ff0bae2bbe5415963070055b3aaa08666fa61766cabd9e8873a4bb8a52924967a21b25c902313d7559f73dc0d71e5140b9609e709572b0cb39911ea5dab86c3acc3e8d8b567f25a014f4ed3998c1affbc9bb91f6cc505d995b98bcfb73a5e719aec7508461ed1851fbbcd30a08f0c0ac5c978b0b4dc9a9330f6a89dbfbfc275f3a6ca9ed65dd85d12b67da85e40fee5f45655cba849ee036b6b88aefad458c6256e2c31d62edf84fb08f9cbd0f6e2a3e5c4885f433eaead12f8fcee0eddaea428dd9a71549c47fed45ae7058a55281366d024d6bb97bc05ea8b27f154d835bfe128b9b82ecd35d3358c1ee3ae0c986e71bac3b98a974cb12d91bea2843355c36e50ed2f29a02d0e397b57e89a3a4ae92dd6c6d08ce976a232d3824244523ba80608e61b6411a8fbb00cf97f5b54aafb79014768c1be3f3709437853ab31674f59fc2d4d08ff3b01e5d581e318712c65f020168b3c36de55cc37979b31e9d2c5638d375da7411096190df72226e8edcfb69d119e42f5c6b65cd562274ad5d2d37ff7f88be2910a311575e8a1469d05b8b696a74f538dd4519fa8a2b4109ecfbb58e8a40b9b5048fabb3c234933b0400aabffadba328443d11add364e9fb9e2e981bafc6ed969333373948c3e9f17cbbbae658c17247a98372024c7467f2f5dd12248f1fa479f2ff466127d4e6942b8d436f1ad31c6a1e1dd7a9f58a5a6f328cc94dca3a7c35a9dce19a793960ebba218dce0f32e30de6ccc440ce6ae2fa6b33cc07a18f3eb77ca2090685f9c90b788e8fcf83f6637b77c145e87845a58faf758b03dd08b59fa3dcc671480167a33d908436d2025f1a43ea5fa43a930b8e221f456f341ebf5585f0d993813843cb7c9144265d28af262c2eaa0f064e843aefd4123b62a42455f26dad367d8011ce1dfd83bc54500978c636f95589b2bf6e6d3ea6e0cc7ca732eb2103d4087ef5d3a298ef710aeed76bf01d7f8bd87e3855a0280567bda07a22cb5d8fe2b4f208e9dd037c4cb1faa02c17ec9cee4389f0031d4e708a9e51c64c9f68a6bbf504a134eb609915978d5fec67b86612c89c69b3fcfcc8d9d4d3f57a754b5340fc16a882ec19e625c11bcee3b53a41ce57108f8fae28a81b289327365099c8989d53c7fc0a2cbeab2a3868f82202dc5292ccde0cf3ba527515ba44ee21ef89b7eaf07cf20ea8a6db827c46599f5bdb502444a39e0bd964d7d9576f7de8198aacf4487668fd0b11283ea5edd71ae9554c4459394d289ced62fab9141760ef255e9a275f69279f94f64b1e58119fb8457b789f19131a935b9dcc1f1e8ba057017d4fbce245a7a628b1d6843e85810905774b637d3c65ba2d8eb8e2585b73fe9a8bb3e93632ba6a3f489d0ea573ae746042ea80be6a54b9e079f1c7649963833600e3cc7d8651c83a093d248e76e5ef01e5e6103690e3afed8d1261b08a1781e0c4b7d515db12b366734f6a4a71157190fc3328015fa4dc22431ead6d2f9832da55805b3f5789fec6f9d1fae6934c99ec632c4872f004e49894ba82e3da870d656bc3bd117cef1cb6e9b13fd4c0eb564726073560c336813df19b69d5a814e684d43f14cc6d6e76e819cea81a1cc4999e51d403df97ff99167bfc959cdf693bce47d5fcc8fde2276622e0af2462c7dca00b9d1d8ad771c883b4d53015d02e9574c462f57e1093b591ba79fe3849938899bfaa2aaa02b41863772bd89bf4b92d5cc0c2f4a82466a8dd412250d21dbb2d0e6e4b60aed6291691cc39e0afdb7ef20f9772ca1ce23ea04f0c9e3bd706e6a990f7f3d69c373ed6e9221457a46428ef957c4b73533f36d06fff9c6e221e859100a845199abc20a07fb3d18f26233d6e6a474f01f53be8779321613454d616f7ed4a3ea4f116a81a374fdfa8335e716cf1d27e5926ec77d8fd7e700eb34af35244ee2004d4be0609584d293eab71644b77fdd51f0148da330ea5c4306811dca516713a4ee8f8e06fd43796c2a68d151b3ef326eef423ab0a25b3dff4d8ad57c1a94fd8a8df7760a442cf56fad8b817cd22bcf887890c18b10cf16046fafe09790746d676243a8650403fec3d5f894c4b5ea153b5e8d1c2b94bd687ea0ae249c21424ae20d58c3184e8edf537ce4c7d515268ce18ed1c53e4198e9785f906207f3a9bfa0d345d587983e2d244cd294dd72cad64c9a8cffacc3ec55e88b981f74f9470ef3bb58c455dc0eed8794466ad72077edf66a9d886a429454242a3c4552406a10a52af2bbb9df3d7f3c6f79eade49cf5e2649b1871b4df0de5ce1b6c78c3888b3c68fc13bf9103f2c57e7801321746a4cd768cfb39f007bac74c7d2cfaaf01551a51fc54a02cea6d5209e259df4a013a6d06e61a7ca98dd0e198a358db65284b5ea9febc3448da743545160c1b63cbb56033e9c3d6ab7245c0a7e3ed37eabced05051f03022193a4f66ace73b75199019cc2f33f4c0fc0d43cfdfc594973acffb8a6f876437d5ae36be2771ace9b339ea16cd9629d35ac0b6fa4660d6b107e86f6a0c3e7e668a37d0779c290f3e9fd59a58a986a4fd4e24833e95bde6eecd94f53470f89fb2033a2925999cd53ed8cfe6aa2b2b4bcb2f213293b5603f96ead2085499b3fc5891f539e3d98f9f90c413fb7bcee40f2846557738b4817d68881642524c73448bab74045d05070fe905916469c0b63e049696000ca4324c1700f26c87623ee56f89cd04ad1375e692446c79bc45425f08024d26e0d25077da2356777936f2f6cf07f2588c4cff1d267cb12d4550b9c2f21f8717bf298e96d30525330405e26ef12a23f09e10f75e5dc678ccafd9e9ca84c8832c9eced03b32a2c2552a3657b238980404fb137109c59b3133f899bcbf8dac5174e6b15b074a12656e2c72e4be1741ebdc20ba77a8e587a633605386704485b9d991d25b986417c24153c95ef7c4dcad283ec222e7d53e4445be3860c66734cff0352934dcac2d0ae3fca291ebfe21024b1c4697490f3bffe83c4dbe31171504fd07abd57ce8ad57a078c86c09965f7edd8e40d32dbab35d985ff6f6169008a31dec9fab3b3b31495c89e70313b53bc09a0b800e2917b2e09fb837d464ee52c08cc10ac9a96460c376aaf591a44704020c46c6cd62e6a64551366b4f7253af76773f79e21deb7944a0e62f8e6a04b34626dbc1c0214010406dc0f7c6155debff889dcbd38a67e96cdb8a11548e6b884689cf5a7d13485a6be276819bbc1f86994b20b31793ca95372db56cbed9a6482354f56713ec8309d6b6817b40823192db59e9e8cb9c0586dbff333084dc999f8a7809d57d5cb9d2d61e6d422803e1889a42c2334699e98af35ae6b9f810a948115340f66fba232afc23c45015d12cfacbffce48f57696707bd57d047a6fbd1672b646aee29871a2233d6b2810c317ddb973894960ee77e57058b2d55f55557352ecf632dec0061f3779c605590449fe106e88c08401d3e758f9a26a64f5c5272dc7e6aabca7f417196e8b4d8bb239a473c6e0324ef97645bcb959bb4e66006cc91dec557572da6a681a0e7fb461a73ce905167b0fa25df1ab63bd8a9cf5aa95e4c03701af9386aab18a6bc1035d95b2e1590f53624a430e15b182a9053b49e4f8d878f776b829bdb458687f6db65d9a058c04386cfefee973bae4275beaa3bd137ec793e841a7d211149107ef04e8a9508faa7e8dec283969fba27678c035001dde8c5f714b92efefaa292d07b2a9bbe973328464a94643fe5ce9c7b16b19ad4c056e130149ef4c15ca4e32c4a6b7b002f42f8b83fcc7262a152e35a1cb770e49970955b9d06255fd03f7562e13ce58a683113c92de52031532d5d6e6e80564b409ec34ca5473c532b116a46f75da33168352749af2d7af08fc7dcab60042ef510fbf3d79f2018aa09bd94c6841e08fe5f1e7fc86cf54012b4dc414119692ca3a285a1c7ed8c23a09d6a52ebfea293ffb0998804bb165b5a58334480be1e1d6a68aff5c87c1ecd491e9c864d80c685ba83bd00861100332db9dc56b25ffc8e44315e04c908221101bf5af161a8007fa6a561db172581cc9e86c24b55b1bc86cbb32c8d5f574db25bca6358f8dded3a7ff06e9ce0a4ffc24731a231e998b539a4c9fdd79396df16b9c09d95147a478c3d1448ae535e8c68907ee0076d0dfe235ca59b971e8af9a7ab0ef2b52d3ba8af1f15238de567148047c5675f9ff980ff5b837bb1f710e31678aefa3212538494c9c41d532eb376190b453671547ede38a757108cc99dacf85bf702ad8bb9339e883fa9056146e45de11c941718f200678312840cfb172ae037234f2ea78475f7c143029432b8a94127d85f5c6de6bed3e44d227c2d596245411949df93c6391f5a4ef1bce9927fee9e231b796a4e9794021d731d8ac61c468328317a77b43e3ce1d472a392beb677bcb8899184bf399c47732e7ef8321ffa31b291baa16f00d63501182ae9c2a62d9b8eb9fd4db3b49a8a3f0f2738d2f459d2574c53fdd1f9693ed65f1f7e7a3cd3c8c92695c3b5f0087417ca26bb50a4f92c568634079a87cb48ca1164b28034117ddc644ad33dc3b0fb7d062cc33daccc18f259dbaa77199fa1b9bf4647716eb10670c2107d6f17e9c74e21297418404d6c96fbc1fae8e0b07ba65b86394745c944495647ddc0387e0d27debfcaf20d5e1a4a40732b6286eacc6c946a07c022a4bbbd6ad62601d91cbd7733e93bfe5e9cecaeb29049492b0628cc3efb595cb037e7ce638ea0292235d75cd95c7fffca0526380e1b1d782d1d445feb40c1b2017c16761e97d0678b12bfb191c6caa64ae5cbc1b06757ab983ea5326292e645a1711143665cae7fb543d77eb7d01fc60cab66a22273de3c131bfc1aa18efebfb95c8b7303bfe511dbd7214c97f3db295a35f91ac2a3779c906a3c99b69f2572a59ef01fce020443b0e7fdbdae7a60a52f1299d34468e1fec793081c147bf0357eb853dd31c28911dcd155177b48e7c76eb75bcb7c8fee71bc4ca8f2d58472a4ca92d09ccd518d64b6f488f3419871047b2e8e297aa9060f6a8729509ad9036d7fa879fbe32169a37fdd731299e5586618b833ae8a8ec4e2e727db1e1590855dcb57a0cf26f9a7f948017fe6a6a4c7e18230cb529cdcb4a8a829ca2c15e4fd97da31dc1e4f110417100267c228991b647ce2465edb65ca163fcdc07b07d74d9dcb44f7cc136b75f73a93bfe06c5934c674a86f6a334de873ccce9a56eaf8d100892659963e5c131f1d7dc5b38fadfa07b9f1c22e23525a373cf094b5a15e111b6b4de1635672c04e71065196a54d432586b2325e4f6e6f81a19e6b9c5861bc68328e9d06ff7337a0ced50c70810c3e7133c00a0cdd53cf7dc4ac155e805666e56978485425c5d8cb7075d9292fc362e4f839f64125eaacd32c4beebea79e6a66b20d00b49836f8a2cee9fa36f5245b5a43c6ad1550a94419653a642f1c7447d9b11ca47c0a4c68621400a751cd4b26b79d8f0c586948264ab27037be49e71c8237ff4791c564916835c5ca41c56496f2a19ba39c1457ecc525ecaddf0f0c7680349bc93361cecc1aaecdf40acc640c793edfc87998a68f6dffb5de160577650b60cd57279eb31889ec29bb2d87c6540275db7915125a8900b0a6bdb987cf7d09dab9c38761ffec57cba486643a914ae23691c545f8a461146a801645c94d3a738d26fbeb9e329967aed361ff1da9d8845cb3fe86f490b12fc6fd96f42fe8c3f7643abd03d43b36a0ee2171cc2c5a96355e6ccda80bdf6e2033b739d3495f06abacfe711601e8bd1b88f0f19e90d945a56118bab8bd39909175b4be8e99fa0f6b2cf19303a4d346932f52ff45676208ff45af6156badc9619b0d7683287ff694d1da74dc61ebf89385e48f8322f51cda3d55b5b47f6acc3d97292f746b52b167ecd6b10c542e60d5c6f414bca4b9eadaffea33c21e83fae1093149bdfea77b05da881d63174e60e6c81cfe6c17c95f5779d919a9bbf2c2d531fbc6433e1243f0051587395e28e06906cc9154ff9f5d94e63955b3dd1fb4a87c93872f362f96879fce0ab05b874dce6852cd82e242bda7e3d1c8fd81f63d72735ca5f5e81f9bbd861b54cc73da7e08580bd82dd891df73e84ba628f4b246a2d9228630e85a4f3dd80209abda6a42ff95d83edbf4d54d3fcc6f675ebc790d069c909dca25f10d94dbdaf359e902c5c3a9e13ec445f070b1c2b912e9894d3fc36cef314544459fe2deec5b89fd2e04341dcf46ffb2628bd8dcf9dddf52c22dfe8ad7526cf1818bd9b96206bb1b580b6204b6d1ac181e8a219bdc391ab9884819f4a3df8c249200094ba3a59504d5b454b2fd8af0662177bd8a18998e217769d80fd3293096ba2bd7e58838980909053262c7a9476b10559adf44c58ede2298f3b5dff30cf33ff0585ab7f27f0a3ae3be793b3e7571498266abdcfcef916fbca7b813697f9c4c266c3aacc429bb1a717781aa932d1a10e024abbf4e51a1e1d5692b63f38db197a2f4f9f435f828353f6c65e478bdaedeca6a9da00307b3198d18e20a66dfd1a12f8eb39a5c1a7d6ebfc10a9d9179aff5dad8551e6f30cb026add6a5801293094c2df0b11f60af925815ff3b57e2ac936fadc5efeb7b3667fe2ec77da31d77d88efc0620ea6b4473a27d8a4dc234fb5d20b92b8cb074922b65e2dc1b4c9f346030521c508ff4ac8099d049178d24753a798e275d104b6fef4b53af49a575b3dfa58815def0f16e5d9c9b4bde9f21581187cd4aac5756e252d39314a0df33572c6474045070baf82f783b32133dd18ec1dda8dbfbdfa752b38cf876b3e924a7f932aa385b4fb01f85a7c415612c5b84b36981c813ddbeffc57af5181145c3a7000bfa3eb332f161c9b8a7d790b7c2b54dcff691251c0681fa3c4f0ba0e7c5db25aa6d55437787ed58828a603fef95ab874ab21064f65dabaf29b96bf3533acdbbded93b36a384bbc5c2324d851aeb2b8b9fda66b51791d620e3cdda2efb406aedb5001b07caa27d39f46898d92a92c08acc0b7799e253edc78dfa70bad4d786c0764c157dc76848e161f52ee1595f0432acd012a340eeae1597c27db7ba16ca99d5130646f44186c5b183b268be3b8cf388573c3a6c71c8227df21fcd1a3d4de633c04df1f0624a8c3df19d17cb416b6747662fbe4819e3cb868bb242ae756902d16f12e30dc98eb8a4b3c3b924a8442691974ca1b3de9262f22c364c50b8321f64164e635a50852b7acff0932a025eef8f2aa5f7380f5cffa8748c7cd83d4a724df59cb2f336c9c9b55f42e6c9be860e6b62076df444c8e6192ee5ca487572480d8c3000fb5fa4b759aee714389a64f9ad987d1c70994140d07681a96106671aa94d3018afbe677e7e9714a93c9db578d10273468acfdf863c1be647ffca33b4ebd9bd2f81b04bebb373e1556f939cdca5c023c8721e5d61f45cdec383292e41ae1bae4e63595c50059223ea4b6986f653b4a8b683165302ddada7237092295b6d31b427e9c1eb365df67ae27fda51823b870ea519d000a9d4d2b1b880589729c92215022ce47741d69a5fcee989d6e2f744145b559e18b1c7bd3d473390ec38b8fc6815412b21b23f45f6a32f375a1a5656910c29d66f8675d5dd0fc9a3f52bdbc92b49f48ef32b85726f4ed2c6c41ac5bbdf05509bbb2bfda732fda5366ce5cb2aa1e1b5d2c83c1716914af0f6f8ad37325abc33c6a5faaa96bd3b8e44a11199e559d19b7759f806797da8f3f103c6e046848e035ebba1bb8a7cdcf48657349dd085e3a5d23508c85f1005ef4437a7769f3451c86908f2729a07b87fad2559f3ca94489e5e1fe378dd68df5ed31a161399bfca1501f0782e2dde8f07ffbc1c1a1252b56e6cff5b23451e211953142519bd3e932e665b860b16f482f8cc95f79ce65e5d1f7ac327a9b86b879561fee873162bd2491061983a53785659a03063c1dcd45f0735b71634c6cfdb8406111129dab7e8b933927ba9ea4bf78f6292b07e91225ba9653edc2ca3583549a5503f1fa941601314a374afc3b3b10be2a50159b4a6c2fb03a88e3f3204db88672343a6e671a80fe8649bcd61f1a49f8430b2b33894cccae918996bd20a19311bd069b568332200f86e0ce158cdb45f6d8a65d42f4749e8946f09e97d306923edc280a238938aab21eb9efab52242a37515154c2f25f42816e6e9c018735ae45ab8b23709e6c5e71c5dd599ccebe497f81b95e900e9a8c70d08185fa61bbe99938b0449bbbb8be72ee9c907062b7384c90adb4e1f51ce2ded5de01a21c77e40562adcbbcd5ab905c816a20261d58173e4f4b5ef57d94c0c35d936527c57001e9edc793273edab0a7331de11041008fbacd9738235c120c03346393df462ace6f54eaae417e15338b48edeb30d77f7505fbd3b03c00dab76d0b9016394bdae9b1b6b8556f38ce36598cfe06990b6d8ebafac04464b418a0b21185b9fd9f5c821df4de73a668773d3ed396cfcfdc7501a759ad10a53a5fee66efe43bb2af9e2c907e3f02a03a0fcb3b8535b7d7c0dbb14f66e81cd0cd55b93f4071a31f4fdc72e0e6751e9cd61b9adbe4cea2ea6cd678c087d3cda96fec3a65b509a269533fd7488d671c9c866d274492c566ba6297eb9c481b34df1effa040bf046a07b1c1b686fd88cf6f33ecd7c8a4b7ac697e90fc8eb981b4b6d44fb30e0fc408acfe1b38324bed99b6f11c0f65cf5bbc6a26ff5f26dc851c2913fcd28777c45b63161948267d886178180a93d451b19850bb25b26fb7e2a60cfb3fd90a3fbb91a14750e83b7b19b810878a7a391faaceea327fbf21d009a400c77b5fc109a9c6ac5ab6416ff7cf74ed0628f4bb2a70700d41e22d30d9c06ad076b9b04df52f12fa33c0dc7765292bf59c66a8b97373e54242e54d28027a0d58ba197483bc5086e7588132d2af24cb007f9e37e44fbc943f4297f367298f1432b7889f6c09501b9d3adb7b06278d00b72ffb27fa016c4fd49e70b58edbb63755c0c70294df2e54d45304e691da3669ca8d513861f6402aaa324a99b6ddf755df3396b18737143d5df394ef348445f95c8a159f69a256b0a7c25db4ed09bd4faf966cf8e77b4dfa397c8daca9bb6a3233e9e443f9b7869e566379b2728a44f459b6afc89a21d3f3874115023cd357c6149aadea0bb5125fb7f682d62c4419bd1770740a2542d02858f597720242100d3d8dc69aa756d24acc16e2ac7e49b621284ddcc3f80b98ae7dfb9792524207d1becac55af7ddfdd240955054a651ba483ac6132f72978367b17e957dcf1efa04a03f18ce560f378e951c79af420610988119f72e85035b38aa0e94dc23bee79ea030bfd1fd6858feb37e936de3c02b20783c3434f7f17f08de776684c4416b92423a30a18bc6e2c3c6d8fafc50fcf84ee4337382c901e2f7f3da249da890aa59be427fc2a805ad19d50eed5e6b4961ba36c5cf224579edddd2e087e85c7dd009668622c29750729d7b3a47fce6a78e7e4bd0d04ace2da42bb2aaabdb47338f18d03542cac9351444f9bbf516bfb3adfbc30608b65de6b1a36653232206a5b878179b7099ca8f2db25ac39f9444926ba5e7edb13536835fd18a91f7d4448321e69e473efd53f4e7cc74bcdc1f9462e85a3da96034af3f2b184e6fe9bf518198f3f8c53ff1ccf862ee5824b2ee03a5dce352805ad0f06b867fc30125702004489f04eb679b279a4556b37ae733152e1eff9154816bf200990f3dbae587e395016ea1a9fa4e88f30ff2712e7378437e3ed8cfc2510d3f41e526c216ab064fc1362e0a4532ee3bca8c1d3fdeaaa047868201fffb797d7347e3b016ac07050841dff347c63ec907bf86817ab6909aa97baa63d1d3d0329582fd120ace1d327e377910980dc2f23925e1bd61a571c88865c30a54b66873a6d23402ff74f30d73f611626a6c80528c38bdfbc2e7c82a45275a2687a7a07aefb776db06021d5c47e06ef87b2ea05c4dd55e8a46d63d0f1b6f79e0c6eb70c4967b5f42cfbca96f416cb41fec21308a63a2696441a2ed7d75e83578c50f3e9b978c2df7c857fdb1365a586ed0ef98db7cf53c126851d9c253cd650c07e4c20dc1c200bbaa80d4fdc70cedbd9bbf284e71db2936ff548cdfb656c7cc3de8158da75a0741fe120eca5e832d0bee652408cf5c9027b7ca1b97ee21d97159623df37411b850956ea1065a352e87f39903e4c9d0ca57fa36d7c05d212ed65c2c3531c300c88c8806f7ab3371c40be872c61f4d808b27568ac230cc78136a03d98dae52e84b13bdbb6fd72cf481b9b4149b4f096c42b56d90fd44b1e6811c03a02e02eed2c4cbc24c82de3ff58c151aa86eac0c107583ad089445b9d861fd748810853ab652e139205073dbdb9a7889025193e0f33ec1c1d4738718a89c911468efb8d105eeaffa948736e113a5362c463c9af3bd6074673b548cf66e4ccfb9c98219e018cc1433d1c8bf9dea2f8a297b71a16fab3561df2fe1462a0d295f1a5bc3a2f3d7aea0d8965bab148b888acd46b7042ffc52f9b96cf22c7cbf8ce2dd8d5de0ab9cb90f60c57a48445d793bcb5bb8ebe979f35f05c2a6919876cdbacbab38f4d56a4a8afd448faefd5962355f265f70977133fd27f48d0eea90d72115aaa3d92abdf0b99973a7e44b36525c2e403da3b30e8abb360f483ed64713cfcdfd0e65fa853bbe34de08d86153187ad373b1997d92a5f2714c6a2b0794a4a984a7e7a995bfab38c6299368d083ecaf5449f28040ea9b827f98ff810172fe4a075a29b4d9409acba337dc2ae5288b2a77df72b90da19ba48e5842e8cda00777ec2b8dc9e3b086844dce127697c496258d680d9a5d853336ca8660d6aa69b0cde6d7473f5ac708dc6bc43f9e4da85c78f60acce5343c37532837fe279ce36956525b8f831b7d94e2f505210545908198988f8af69f6e5ab0bdf71e26d0f905c6eb524034deb5713fd4e21ea6f2ce1b77f7917b6a0ded87398c2ce06c29cbb3e6fb3bbf54a3d4bf162ec914957b3ca45441a6d19cf44e0071aabb9cee5f4d5a5d4bd9bdcc961e25a449ff6b7502d30ab3cbf85440e0ec01a50e0c3a4c245504b0ee323594e1033b46a5744ec1459d861d4c14a679fdf7e92af21f22cdb30c7fc042493f2fc644ae1be2b1fbad5a16956b59f2c17a2a835c8db9e71d74a25d634bfb668b065911bc7bbf1f0c4dbe733c097a5d43fb7cabd307a2725a50eb339a8581c004bea788c9ecb7223fcbea24b66e21ded6a87ad6da961b28a37c43d65131c6b9b636d418c9375d3b4bdaed2349106934af17e3c2c8d4c4eeeb600be834db91b87797c50bc80f4c49318b9dbba50d589a49fa088cae8691961b5edf75551cd70a75993f7794aff072aaf20a54673c30248618ce8c45d9dbc03eeaf62e8cd69476f6bf95d7fd335495a75a0c13124b1fcbd6b1d3ae05ce30fb01009dfebe76b9568b820353146451e8ab7f18d0f9cd7262fd7aabc5965e3ad44f0f76dae2f80b7974a72661214a6b8f32673a80bf101cad7280f0c33b555e4823713ae1876c3f1f329fcf799cbfe7967cdeb5fda69ad90e2352e50736ff95cf3724176df079c93565faf05c940f0feeaa117889010f3fa0c4593c98c36664915fb88d63e1e609b3df5eb6c9fe9e10e222963e2392bf10a5c28058a9301e32cd0bf4478096dbd138b511b2d715f12ab2ba22d10ca4762a9bc1dd556bd1fc44541eeb080e8d286b81b47c1d9036938e622349ef8e2c2a1c8e0c8b349521d1f51dc8cc8deec9265ba88577768600fe1068c9c4c7b33205ee08b62cbe8a67e5871d28952167e72ad77033fef839197c417a99ff028c35ce3005bc82cea7a7035033be46a39b4ff33dd5f48aeb0c0bc0434fe330967d289d101d2ae50cbbb8edefb382f50031e000f5087c2a38f97867317cc9737b345cfabbc3f19f52f5db22f410a254e943b21a885b569e502f9c9c5398444e459089a52a913c670ee35380aecfeaf9ac91b803ff68e71195fec24f9b23b282213227ceb812f931856d98ba686d58eb55874b2e4a6fcad47e810d2167cc969b252e35037a0579a944a3ae82048f43723c9b7162ff610a62d26b7897eb8542c509ba719b7cc0e1eb09885e5840ae68fb467802fd69d9bad756aa3787c205a247f15720ebe1862703a7881be7c1d59ab578a69669d6eccefd3361940e3a3480f5c03eefc5d736f5af9e28c831c3b4439a1af4dfb863330fc6dcb07194ca33fbff75b1493b6ee12492479b42248fffa7410f3c2d1fd5f47801eb6ad08c554d778c2fe4cee448a045d17b190135343096259046d11fec71cc554307c589997ef96c6f25fddb0c5502628d7bf8a5fe9164a4c2ebb878344856b6f9a5dab95b7ad0395358dd49e7b14884ec6436c7f2fb7ab5fde1c1fe9e922e6c47e184a9524c086b6eaa614a3692eb49c47ed8062625f6e30c5b3d581841b161790001d0b1cd136a2032d5bf763326237b8169e383e2a1ff3526080485354500538372ef8bd0935fb385b0f67b3f6658927ea0bdeb7951456f44bdf1b8ce4cf5641b5b4aab871c06d005838a6568da1a75a919320ded1c9e0064bb92d2b57533fbe549db50935ff955a3d2a2d9fde9e0185ea87051ef1aa0c5835cd02c5bf825a9215fbfd5865bfaed02d946735c04c64d677f818c5d1c487457118c3f9ee649552f87ac2e72782277b7cdc9aeb2aaafb98118929358c85fa1357a3e0e66069aaae15cd600c24abb02396c35e1969866af208ccd3ff2b625bddf36cc720730bcb804c53e4136083cf176f90a87bf4277d418fa523e9eb0a74823ff58cffe0cb2b1894ff664deef1e02a62ac6cb38e3015c00c1689b0d3cb13d598ddf7f0ca2f21f27a3fd1ceca2f0993afd0775343df6033e43c69dbec3874eb67005994b457e03d300e322ae6fa25baf8c069039e9281fd91b0f929a430c4274ba732ad951421f33dbf8cef4aa62e4494b72794986a518360a4510a9f9157c564ad110a1452ba0780bc89402bcda9f0ee5443d16ecbcb9fe9b583bd864526d13dd28da27e75438dd9636e2059fb04a85145a8244d936c299f47e82384669402be49df80b97f22bd999adcaa6deca0b894fbb753eae0ec41390767f1624395ef2817578e5fe7b2a2c7efb34d683c2de945cea356331b040b94c687c487cf4361030cb11db7251930ae4eda335a75523e0bd2b9550c6167377c1a5849cb77241e8b4776b98c3b3b992ed13641f331a72c62feccf4a0712b9301d31cfd5aa67e5e423e909c6ba494407c0a231d8877403f1f95dc90674dc0a66a8955ffbe4b2a519f8519862aa7afab0519b816e3a418c391539a07a94c51cb9877098a3c8738d8a9d6d08868ac5747d1f3167eb9c7469bcfa55693a05c0309094eba89b7e4ed77ce5a26d0de3e95ffa52bfd826718e66ce961d5547ea778a8e730675f7bf8903b7188f216732a71aa43b5b7d89453ac7bbf8c98cbed98c97260c5e312d83378051698fc8ec030af1a103e515f911b4261c396f8d25b4580c2ebf7e1356063a364e3b0a7ff96a433da43bb0a2f9705383f31586267510e61612a94b0f3ad003713bbefceb56e2a69124d1e63a0be8101c5b744c045f31dc9daf5ac0834538be335e7c3e22213e797ce37285d569d9a1cdd0fc68fb069284f49f0100df9f1f51772b5ea0fc164f2fdcb56c18f6645430fffc2f1a78f65faa382c648e58cec0416323bb5847b07210fef5d4d126efde5f32bf231911d6a097ea5912634051bfba681c807242c1a3be86b986bea17dc0ec90f1bddeeb441bd3e09e3dfba2221eebd75aa8a7c510738068a5067e6983de598966a68162df497c38b03e9050835888304f9429ab9d4ff57dca2684f66802c2a88a0e02d82980a859d54fd75a6a80484861e81c9c1af4daa53b3cff6de95a8d0cf991cec1cd96aa1a47b0152028b7e6f3f77dacbe3d58a53764925d5aaea1300556758964360db9a9aed316bdc6cdaec48b2241fd41170df161870375fa73724ddf17be06fb1967d4833afc9158158733fdb1e40fcb07343238810da600af4c992cb2a991d1d8d43fdd27ec247ee1d6a2086ff5f2aee8485fd53d143fd1c2c1df814d3b7a2254919d49c135d58e6b84b4f5acd8f48bb595bc7112e80a7d9d9bed8a4c9c4928d06bc16fea70780492616ea89864d33aef33a14ae1b8fa9ed1b956f8c9e3f2c81f3eb4280347657fa764c89ec32427fe8516dc455c45ab4d785eac9e19a7bcef428fd161ade4c13f9bbfb976f1ecad00a1c8da5755703835955eb85bb6485d781c893c31c9e704c859791ba67c914f0a75e81a3bb15edabed7a07b2956cf333c8f8a8680911f8da38a7d512f59fad162243cfec14b34014cc6bc71e79bc21b652636262d2a88ed401782f8b80741ee46a89af5e7071fa64324e11a2cf8548f071155428fc74699e19d79e3e18804f2bf86cf560114f066944048417c884cffda7f5423ca1e708a877fa4b72edb20001a95706dcaf5e3adb76fb28682e12cff86537f658b61a6105c54b3a715aef1cfd7fd7b5a7a604acb2d26371f019eb770556ba6108e12e2a2bf67ea02fed22b0c58db9400d7564aaa2726d5b239802ef684253cf855bc380ca27ac23a2e4387a0046ebeb68655617a2f6a4a56299f77853fd0f8b1a1b07797c5fcaa05c7f5b06a57c5c173a7b319435d696dd10c05749a1e2a6a94e8b9c9f7c08112baf2474263576599a68296e0ab6561e345b10eca6007fa3346389eff5da77e6ed3af43fd7bc06bd79cdc6b646d43f1e3eabc6fd08d8660b194d2ebe7f399cfa0f4273d12a91c8f4115b5d70eabd301cb2280549b871378bc1006f6df69c523d9c879b38495dfcd478aa69e15309e2fd5c057b76b9750810ffe93a764e62fc2a4dd33bfd996d047c1a24ddf7c12eca030593a79a71f7155d3cb1e460e0fb3c37d69ac9d12acc9d61cd0bea6a4acd629f50e10c2c01c0286cab2b523ac2c8d776c31734218e10d0f02993d055baaa6346c2d7115a3b2c82a11f0ea1f16ebce248952a7d8c1111951de3b4dd0ecf8a3abcc94941db77a6458f587a1a8866b24285844c0b71f112e1dc28ee9164f4c52af22aa1bf43723066e31d467b0159fb79f7d36cb157de96bb375216ea9e9f9f262ffc1bf28783479b028b221b45bbfa1e5b263d3e4efc4b1e79a570f19ae2434811d3849610370db287ad6060f6002f592ec1b05226b3486024f0f1316a718f5d3079c7a35d3b98eadc5dc3b0e54408a20fcffee5c93537f6b15ddb4e7a9e86bf990181662267f6c1aec9ee67df2bbc3cd6ae7cfdce9729b2ac214044a7a6a391c6f3371cab58ae354f8906d8c85423735fce3cd6a7cdeaeb8a210196524f1e5e4a906af56f6ef74d561451e5fda7e38d65d92e2bb4e4f274418d458fb8f0dfd78fa963f728940fe6619b762ceb2f83114e00069fc41f16803bfe9d501b03ce51610cfc4fc9ab7d2d3620171d9eb4c7d88ec04aa4a70761f78226c5515de8de748db7f68253196fb8f5852b1d28b2f3af41c4799606397a161c423fe7b0a7bc7ac61be2e34cb39408e47d51a63080e6e6e0633a02550ab6b53e64e49dc84b083a9cc5cb52b9c7784f7cd0deaf50da9c0fffcf8c0b0ac15f0511a89244f9572cdd8f5e860fa1f0011e1a38227ed0ecf654539ef1585bdf5aa6d1eeb2b37b9d4c1712492d6d40026bf5a70f9dea23f38f2fb21f758f26d7a23d6e55d0678c45d7378157f270c9390166826c693a44a8917e34b6434c8aac6e8f61c625197ce720452911b5564b1c910437cd1b6f6fecde774aa00b68049263debc397ebcac69f5779aa24f8f74b7179868274e879cfd9b0934698b77d0e0cd428caab8512e3a6fdc56dde00281717474542d191f8ce3da18060a9c7cf0ec78836e2b8a1b718f920f5a2bdc4d0bf04074644b7a1da6c5a887682c9e47d062c3a9911d1d6d670b5184dd08ed1d4e315defd40ecf180ce15f36df00c91183c3aec26141b93587a355aeb13d6160b4f3a8f8afb4b34fd129c8d302327c95d9a4c8c2b5c18aa68f5bbf260e3c3fbecc786f6463f76d1d854f66d2ae5e49d82144ebf074e555e5bc6640d7ce707cdae88cf1a196450d322d5f4ee7e7f2479a4040920e100928a6c97963ce99745d4c57aa2d809487700a4bc6c7cd14c00374fe3c8b0b7c23d27719207f559bfef4b14ef978065967afeb07aa8cf28046153be4b61a4169d06acb6ddf09bb7de5ec253c80abed0e9d0d70c42efbccc013685618a8ed1223eb910faac82b725bdf582d679fa362a26981d54c6f1172938a49cf50ee33b99abe850a34505ed26b81745dad6adfaf4fe329852e2c0145bc6ac10a2f0608f9b0ccb9c0fe19aabb549ea6889dd4a6b2df7c83639f058f21f147550f95cc7fbc458f5dd910d3aa1cc22102996dd8534c5ec626ae5d4fe13a76c71e2eb80fc0983d67e32c1274264a1701bc6859e2c7ba22e49d6912440d7b0ad42638392323bd793c753ddd345cf10901d484014923e857340b81ddf0820c4d2fb1f0752a4a1ee0f4d2d20b698fcc04cc885b2a8d5d635ca11e04a0697e348e96c562a0f242af210341d139e0c1b03577e13c84d23593a1f2e9e71b734b156ba3a6d76d8f169fd21b4152d66ec3f4b170bb4dc844da547f746027ad02ff72734133bd3edf85c31ac2356bf3479d1784b3c69ec6586f79fa15f0654bdb89e79cf1f0de5928fdaf930f711ea998eebe32e13dde1ad80effd441d3ff0c2c54c86a38ffad5a3ea9d3ef991e069011f81c7be66b00e5851e9d8d3bd727b9f70079ebae6e44906589e53f0e891c03bf4a1d22fe788cd58ca8739cfe218f013878987e3925b8813aedce9db55ecc641e8d76fa5365bf7853f438dda0f0eca2d0c23268e4460389f3915ac24423ff87a2eee2eba6ad0ed932a23cad4a15812d6ad8d7e663880d1b7c8b1dc02cee629572c2438eda289c390aa5e38a9f21c4e4e91590736b7153a92be244a79db1cf3ff07d1434706f064ea9e4ee1e2804b854d8a3d9c3b4419011b8637cdfc04f66c6ecf84c0902cf7777d5c7e85d29f9cd7ca7ffc670f2d3ecd7f701789d1f81d137885e1572a61d1a0749c53eab6f46734cfae7936a86d0643db620e66e6042ecdd985f4f41efc7dbb0378fcc0c85d1d8559b3da90c4b868162e0143b4db3b49a99fd36e87a382cdfd57f98da39cb21c230e1faaebe6e6a9b2e36b2394858e3331682ce7eb7ce7ecc249a99b0d5f9ebe1a6069a6c115c5742d82f690134ae66e064347ea69d69f835197f2a094520f1a079f54e30f1aa476832776761868e6c5e41d4abc07a9954bc7ff003f2497c9f211594f2321aabfeb4270cc9f83bef9fe3366892c3c7172bd9a22c11abe746793df5a94f081298e1ba5d1258891a21dbe20959c1ae1d50ddf2f17098cbac1f2b9f4d241028c5ab24ea8e96a1e9dfb4ece864a5035c1769326564040cd55885e6acc299102178850c6489d7ae8d7b6aad62a6aebcdbc692c1b66429495dfb45e58e0814215dc04512062fc99597813f4e648477d95e8d3f57e9c4a6b10e41e362fdf575a36d23ca356995e1f5a324885844abdd3001f4e1015a79b88ac15cdc341ea211067f06d44d6b47873f0717dc3239d70b6a9769c0c229f48a9000f0f0674e6e608538eb7f9756cb63ab850b9b1736da012f54b43775e90bc7b62dc2cc3edc0f40f68915529d5b6b84b64e905f28c63b1a5c9534c25b1ccb1dca124a265fe1abcf8192139d25c1d301814ac307b4be466d517c50415f6238dc1022b817b346b3eb8b65d02b05c6cf52eb21f542b3c861fed1a6ed7be8e84ddbd0f962dbe31f4777fbdf71467afd4b88b919fc08e8bf38bca4de0a5772ffd8b0da61590560c9898829eeb716414ab7ebfb32c3d578ea41323827bdf4cb387ce33dc490bbaab1c336d30092f0828f11c976f45fa26c49bfc231d98093174d5aaa89a906df8de1e09645d652207d6c514262d7ce02ad9d8f5797cef6fd5712ddf646818de822eca091a6d1dcebe11602ec73b1441fbe62404c06f6c01faaf869cddfc781b51b2cf48d043f27434bdbe019ca853f93470c43f6ad702e85adcbf494f57f48b16268bcdcfef49eccecaeb74f2ed324caf9a1ab6b0d6cb9f0a0affa848e28729b8cabb55286d4dfd87ec5991878e78dca496e39d4c811a812a3780c5b5c3f5b4a623e83f7a61baf009dfef3e925e0110903b5ae1c2a40e0ea47920dc4409f1862f191f4e982978eceef3fd14cbf57d87e694fa9e19de65e4e7bc6be4613d3537fb2c649bbee3c7447a4315f74467b609fa944aa318eb22caa116c6d463159762d3ad8a84a797eea6966611d0442596550d9ee861d2191911511e1aaa4595996b5022c42a3dce6faca3acedb9c590b9fd3c5919604dba659a66717d48f75da87874d6c791f3351c70bb594d4e62172d7cebd956186e7898d7e88eb0bcab30b6fd61b691fa026fa616475bf1fa8acda29ef122375bd25ea2f47387d25449893a7cd69a60d8340051646260d96a70d651ebd9ba61a6da363ca09f91607b9b8cfea007ede6f881f18a69b5d9585cf72fa34d30dd645617f2d8e4aa294adaad4181af3a38e4cd7b7118d2ac1c760dbede1d126100a396850561f863aa802a61904df8eda851e05b54abb460d7befea013b113793190f5796548f162a9226374c2b59372c34a3fd896e2aef822ef60b20d18051af0d8168055fd1274d2019da5fd556dc8d63eb8622868d93cc2818023da4f616fb4ceb49847e15d5949247d49e5389b5ed974213489e4853d8b95175e9b545958dfd1001dfdfd2fa18495533be82ad019e274190dbab87b8eb578124e80c32591d58613c6746801c82a0dc3c9179231e3eee6df0b27fdd53faac6d895ff7b086c3b2ef1a6174778d2ede55d86585ba6613939ae6aa7fb6cb7d298b36b1f9bf1cbf51b51ff01f0dff6d21d63c3d1f9680ab4c4ee50aa1c99962cf6542ab3a48bd951c32e45d4662f2ebd0b0a36a707bd97e8771b9368ef416a6f5d849d69bb24fc04c65b2d36f0422d1322b5288caa2121601fb089c352551e33a9e8998283bd6e4314acbc80190d89c37410a8a406d5e485d914b3f14cdfff04db8b0a40b6e185538ad063b2c892b4f3df5d9709b6fd31d3142be07af96a464c868e0cd47849bf2efcd757f2fd416109d44054b82870ae22c8c3e6f7484a184eb6aed3d7eedc81bf03253c9514121e6dc17f3c95c65dd94872258c16d769de3e01e29b71a7693ce1695e00f687bafc2c2a089f0418c959c006d6a14435a5348214f8a12b86228fadc7a6ff8aa8036f7657bb75a8538e1e6aee11f8ed27361d23a53a3f5640cc804f5d227e9dfdefa0a852f3f480b84069cf510135e03df2790e5606948735c57d5568a7f8e8c825b65350ff9303cab7545908e159301ebedbc54fd79cf1898b6a7b91d17ca1b15ca581d338c481b906f588d5aca251b4c1a8d5756993f144b7a3793e069522f10cdde0a653b5f2ff2b02bd7205e492ccbaaf27945e1a59dee45b6a76c27485047e5f2a49be95056f98715a67175be3d89cbfd8877ffaf8c370a2f17b8aa57b299fe8a686331569ca2c4ca28b3b69bf458cf5efe12d4878311996a7a50888b94614df022c616b3d03c618e83066d4a1e907086faae8aadc6d334e30db82af3ea317320887af3ae33d93698388080be56e76f34f39aec8ff0b06df0d906eba5cc7eb208a749c5f100d102aa5d1d69855f7e0bb16c3310969611f267edaa292dc40d6a885efa953a255756e071715297ef5440c00caa69529038067dd5b9c4f0154166db0797a4d25f7f9dc44be6dd4de8e133a4d1f247e278600979141dcf3ed88ce9a9802df57b9c669b88a104c3c0d23e258070fee7299936e5c9ed354e6050672254377ea88e031b73939f287d152575cc3e02e22aec324696f30afd1c11d6f25927d2f6d7b79260a1b2c096d0fe44cb33b3b1eda56ae461e780f784beb58f6876e8b9b313a467a1d2abafba752236c664593849de88daea4c0f3db950fb4355bcf04a9993f9cf1e22e7a30649408be01a9da0f5e4b4a12bcb81a4d1c408eced3c9c48d0b6d42f36a8edd66d4bf2fac14210bd3de735fd54d18a5b6c4d77ced67360411f38276689fecb554ce3813b656cfddbbe7f81b592e07db8d81d42e93129839d609b50f993c09c959c1dee752b00d539f997e6b52adf9627cb6529fa0d75c516884df10e713150878e6ae5c06940d0a69757022791fe5db96705e4ef9c07e745d92cc24227d8796983a52e022f6dc5243e80bdbde804286853ca9430dd06de42a0fce7199398f4965b9af9898707fb97dc3a50a83d0bee8d257d363b06a04da615c1be42239662c8395a5ed7b1c628d0e6dbd166606fd10d5321f8836cc850b2464431e6c0e177654b62b602a2fc5ce66c395658b01e824e47f9eeb861d210c126b475287f33b007a34a383620b14b615da33adab4d87ac59472fe44e4694a3500205bd5ec4cb10199ec2e33806f5df29c810c03f6061078a5c357bf0737e1d9695f550294df82d0c6e4bdced185497918779e55fed8e5d357e8dfaf0b8f1281c75f982b86afba39ba3be0c90d70cefba77591cbe97174ff6148b40531029382d55dd474e22edcde486353148008510851cf26e0b7c0ef896d9361c2acead7a373a6bf54125bfcdeb4b9bc1573b7cd6c02f92f3feb723937bca52cf9fe7bbe11bbba89444bc840ff0938f24896dc5d058cf956bd2c523b461ad4448e4446dfe03a3f667daf208d4d35de676a3627e5cd50882e44f094c379249eb753a1285f46b84857afbc075e3c31c8fcd8e3170946f96135200ed92eb076be555ad43827facce48fc3560c2b0f4891e30cc8197f8b484cf1ee0a93ad6099b3d719f1de012aaf62bbaa977aef0cc224939f05f16ba7b846155885e8c975d8269d81252fd8d13856ac140dab2e65ef25c348f4f7af96da0e7bfc3b994d0ea94a8fc17f713823af0bd9ae469cd31b8247243dbbafd5e4f652a6b320c1be604c844d774aa785bee5f7b10e11c8df43da0f49e81ae5f1b879620a2314b82a3b5df352f1e0a59cf2ea51777b47cc4a798e92fc4b39f914cb165cdb0f02057154cd119497b22180b603668cca459b5665651bc5d33dd1734f38d5908a248f91fdf9953d5b5d1013cda573ccae6bec1e63b5318f1a198e377a8880b69f3a5a8126f87e5114b857d8e03e7f1be996e0630bfdb35d386ebd3e08e56173d8e50899d0668a41a57c1c208432fad2264d3fbac88ea67332804ebb7c47a28e7bb2002927cce5bed31b960ad594246937a13c121e3966bcd8ed0320510a683cc22c396f989f885877f8657f281503cb15b02b530d6fa4b2a14c8fd33790835e4c00adbee71d92a05f11d4f0a824ed6c0085abede1bffa7d73bcb518020b202943048f71772baee54e554a6ed47088a4167e7aba39c72b9b389c846e22a03f14aab8e9cd94e6194afd617f2fe89b45115f1a51bbb0a02e2bf3af961165a0bf03deeab82fd89716c3629f662cd1b36811b8adfb584e3d6ed35242e8356f2625ef923649c71996cf02eae96c6e5042b2f57125dcc0190a936ec7fb649c032345fc56400d82ab449b71fe988e3f88336d5b1600f0f53dd2a2511cdd44f7706e6f87bfda0f5f20e75bb9a4cc7f6e54e56c425d682366f3d87138391df529d897619848b1d3c140a76e8f00bbfcb50b261782d800836d91dbfda32ab91fa19c86f15006ab516df178b6ca424ca5ac61e944cc6fabfed2e6f85943b03d395edcebae32755c3ac0c8f09e1f46c60dc3470b8307971e816fe78689c91240e0ddcb3a00b35e32a35640b84aa70ed6f0aedc80eb18cd789ed3096786de5315074bce34402155077521669b3bd81d6e617ffd4cabd9cd0d20228d9214618b2cce3c199faba55449ec3dede6a58b4fe7132d129411d3e1876b5ddc14117f5522e423bd23c5610320ed9385569e86870d2c0368ca3c7fe321f7dd052a978316857efadc9a9c328f7c4418b11098ca2f319a7c3d06d7e7e0a7709cbda420fc1ad40f41566f5f9ce578726d33d8b2301cb8008ed858384c860e06a203962c866fbcb3f30c70c101b86b53463dcdcca45171fc425fabb31b7d0642386cd22f46093a867b2535693e92475503321aca43fea1b2a8299b45ee58b25e82c01da827b1b56a97f85237ed9befcd9ef32c6cdb86f8aacfd5c91654127b14af53727a1740a9126629629776b5d655e88f2d7d8e32080e5a1d1b7a5856a3b7584c60f387f631f009537261f580c51011c84f411b04bb5d11772a7eafd2b7f7379096f959b8ca1a79b4ad8853aa4e8d82dd406a145f86b8830281a1c56f3abcdda71c3ac68aed56231751721102e144e37a029607c3c4ba795fa03ac4c2894cb954b05ff9cfc124058adac244364d8c9101c17678a04a6764d753b53ba806dbc71ccc491f1478059be4418fc7202dbadbc3d756f8a85f1dca51fed273649ccc0d1835d4bbbdb0e7571be02430361b219afd7e5263722f393fb109106ffb3d357007378557165747bd4797e1094300a3b98ab739663cf694ad98cb4aeb7636b4eff3b397b817613099fc4055637940cb52cf24115edd387724e6505c01f6a548399acc40a0bee409b9e33252e67e7f7e0467aa783cc86e1429e0ee5415a98fe36123b087740422aeb5481cf6bbebbfb711a06b666ae8b78cf60763d80a569ea52839a4dda9b9e1e28e76600c5b142d227cd329d6fbcc0abefbff22914ea39541207e718d2e6c2e08c1411eab74e69f65b6691fdc9bd7bdb9a364e9288702d56cf0db0ffe8cbe583a3955afc6192324afbad806f2da10bac0e1aed695cf97c2aa417685f6e39f22b3bd6e54919962e9acb9c15084ccedcf78dac379b7528b247d042d69fd07acde436987da470878cf09b0dd1641583ce89c3c27674ed5933c7d126b9a273bbd462cacda55d3c624c34f47cf3bfa1f0957a75a016afe6d5b2172601562548ea81c524efb46a3b7b8b52ef19a9d7648e5f0edb400a3de699288850c4e3b0861f4b9f5c18ee7f1cffddfd3c5669e8dcd98c6ea9985be387396166a6debaef183bf23f633dc6c82566a6bc7cab578243abe53967ee20ac50568e1e9106b0d9910e6255f6b21354f3dd14464f5a903e70abe41fb2d88064d0647fd0aea5dfa9207785f8049720ee97a7ca9bd48532767ef7e278f67a18e877d57e1744b628511d3a82fe413f2b963b6be722f289423329eecf3a5c312fb5869afc20420f32d231cf49e531f9044076a8f6bed0ea5af9e044629553a6568b73b005b22a3257d75b1681ebc0cedac2ccd6443d6f27631d63668f629fd489040aa3230064437add887a911d44bdd74a613c5c6f8ba618a196d53c03cb8b8f7c69d5c075004bc61a2be6da603898c6af32b13de8e4138720174108fbd3e6182630136a05ddc0d498653e4089a28331686c832336a92643c885d5288695705dd66f1eecc3080b17ba3382b045fa9bca0e3b2a3be87f1b1c637dabeb44d4021d1f5471593ebe782c4593ca2e630f6af229fb77275542b7ffd3bb86507c5f4bb0c8c17813efcd77e0078bffa08fa182726194e8035e41863a5632ca1f9bb8e69b96bca3dd799780bbd9f5c9ddb140e4ee82a818afac17db6c6ea3e68ce729a022fefbe88748ad9de4fe9bbcbbd6ad00f98995dad351c57e23e86d6318e5c1b3f2044d46187e08c5d5b8e1455789f7eb46c577cf0de7dd919446bc6cbdd85f8298ccc3e43bea653ddba0e250940ffa72c18b572cc3c8c6561b525261744b91e7189de926df9cc4632c1bba649f3b71dade135ec7238a63d49b20d49963683a31cfbfd70ea15c5096ffd62830fda2988a5cc1b87c862659260baf3d7df565247f66d13b0c42fb207d72a5cc5556a7af33b99118cd6667528c19127571f325a8b3c433466c5233797354559f5b73e75dede3221c762e9d73e2779d3ea8a9d41894a50bacfcd9c34b962c68d517f842fcdfcf11f485ab5987cbb504cf7e8fd128c88be2f443a2c11a7b3cd2210c3bbe4e38469528e708e5c3ec2d67758176d42ed86723496f26d214dcf6d94d456120e7bf9c3c1ea02a9f734e711053b7787fd037d789f30b0ad643a78b8e2b4f5392ef7d03677c3ea598e271a105958b6e417c869cb8c7d5e9461041197300fbae5c61f6f8d7dec772a008336a9f0a1aaa99a7b0593f23588e3fe9edcb220c0ba37f4642e5755f9e9a4147a99ab21f7136a95de54c5cde24e83e1c728eaa7ef17e3d9da30320420ffcfbae6ab6406db1303594f0a8015af7c5c4779d655a025bc597649253cad374757ca898875c7fa5c58096795187185316962dd04788ca278da3828593b36f3caca5cdfc9b32f2f8453bb50fca917f500dddddee2fa9a9870311d8ee1789b8452d52b7db4d6c16a57815981cf6432f2ca2b95d498da9788773e07397d96119d5dde5b84f1334f260773330ace51212876632e07c76d5accdb290d3b6bb6d4e936154710b4a3f6d21443eeb9c700d8dc751d9dcad01dab31a817577c7afb9924f464545ba5a9aaa19652ae9bd18963e7af4e0c4d5193748dbe2c009e42c02d4c3f9ffb3894282ab19a39c7791bd1400caec9e14693f23872216548a46b2789e796af238727e8447a25b20afba7b3b20a4207a631cfdeb05db740135685ca408bf729a31c23e152253c2009c1e5a78716995093d04f5556234d03e67d97358f87bb869813ce63e4bd277f02ba1c696dbb5168a954f4ca8f80e0b7126662bf367ca2663830d7bdd935e6f42e0b34bcdc0ac05093b4b37ce339b322c68e1fee38290e9aaf041c347ab884586e67a20b43dcbd037ceb7ef8dc4a2aed6ede6f31bfb241f04917bf7f2b0ae9bc101791e8c2c82b8fc32d029d55f3498cabe1ce99d45638fe9f1ea5394088acda4e1867729530ea4b341b57e3ca175c35203ad0fef0664d6243eadab458d38ac86e340d8810427e284312dd0a62253b2a43be9f9ae4ad6139dcc2a83139a31b7d00c523130d7d45694b29858e9b74a30834523f1d55a5597c747b5cc212f49c879112dedd19e6d3b30d91b55cfe3616085b304efb35bd886128b1a3f50ef87aab3fb66ae17d14aa1618ce4514d57a93703165b56d9625ad281200373523f2488bac3f23719e7a7ffa824ab1b92caadf5e95c57a242a4ae498ecc116f8a16e616c76f181500d6fb2bcb0e6d74d7068a2b6f6269dd0353100887ad31a6a84593a7ebb37f3308fe98ab9f7ed8b13255566c7348e28bd80fccbc54b694d438f3d9a5bddd6df76f7efc304d83daff98aad5b89620b9181c96ef9148ae17191924d4b59d01703a1d6fc2e97d5716f5c05aeb9af35e58c281ccdc1f13ba6d1419726a4d05aff3203c774912517206a60cb381c163ccb02cddda61f795c9ed01526e986bd8cacbb40f548b7e30606d0c7a3f244df92b669c05848b1b7b136097957f00926c27e447cf7e12ceaa528bf4de1b594f328d42e256d6906bd2ec25277d43e966a7b257c0547c4d179cda7fed35c6760ee69bafc9085339117be788b30417a2e13588d73cc4e90a10a51579722ef9ef9965f47e5f78c1b8c90c82324aab9a99c4f07c958f23e09e35d004892385febcb8c16cc528a4fff0bb0e9f7d1378462389039e4dbd899b4c76890d3768f593884501e10701520b7a967a4e9b599ec4cccaf9fcfa70b4d9d6796d3ee5c0a9c86c77cd506ec51bd4de2670d2fc38fec207fbf5f84dab8cb9b6bf62d81ed7f7da777faa24871be4e9659240d77009bc2ea0f79675e083ced7be97d3cc59dd707a05789dc14606b6d503caa6008315b39119979429512b53203aede797ba378df5802b0b268ed4f6fc060d6ecbbc91758e1b48bc38ef961f4305251fc1d5eecf685635026de471cd9116e79763f828c1e7c63b4354c108cf3c6068890fdd86a96fa6d7474ce075a8868ec43b599513336b1769e19baf312ff4ac3fc63d77125ae7fbb2f44d357fdba201ce72029d46f17cd387c2870a2c5c4c4e3645b847fe5d66c40fcc4671aad52097eb5980119921cd803e0aa8866b35d3376e6e2d2c3701844c2f8fbd4b8c4984fafd28706a3e6c6625ebeadedafa71d97b50ffb817fd31485a63a4454bf946cd4a3870ae19b8d714d85a000ff9483226976b3b40e082bb88bf3d57dca97aaed4505811c7e05fe433fb16a2a55d50db9cef9b4320de825adf94cb1dba9ef0b44dc3482db1be6061908bef1eea626f56af285e7b002776831fca3e3b8289e320902dbd98fa3d1b9386612cb0a9b6a91e7135a785376d781188f0b6f5dbb52bc443d8e24b2503ed601fdf93fac3ef57c0f03a93bc14e9a1245c3b1ad07ebe60d20e2e7a6ecdae023c5db4817f8310c23aeacf446c290c1d6d7fbea5d533142d16515ac399bc1a1f8e7174e411be633c7a29a29b94c152ff36e60f1e5c638e2e0db254e59247a5fe91555b43f90a486c9f66c7d2f4c65e1a1463765e050553fe5f72ca58fa6c274bd4f373ff4d45a72f1da095c68f0484416e7ab4a6b0b84fa8f5e937e020a4994a1df16657e0d1529113aa4f85250ef99543a42e5ec79787140ba0e19cbcf49087999e3600eb1ff5a932431c32cda550e2559da1773af6374b2368440260784039e17adc3f5759eadc94f45003bf64f663a36bd884428b944614828d9cffaf0dca00ffe5fd2a912f04dd3730817f781a709011875f4a5a72bcaca2a028af49ef30ebe7d3ca9e37fc8f9f98c4b1ffc7cd4cf9d3c32b468ee4f3579c03dc407b94e44b656abbfaa7d70f5f4d3df208425f03e7360d857ec8110fc330caed23f446fd104cfe6754bba4f77b0ebe86b2959ea210d096a3790b64cf445d84b1ec90b5827131ee86752b961aafe5770981930cc3291cb174ab40a5ca3b8cd9a6735e9fc236c86c703831f0cc310783cac39ea79895bbafe5e3150bc45cc8e9720799bd5e6acfb9aa51115c005943873c52cb8664446e488eda8e483189a4114473f69212c53d9c4921830797af20ec38809b76b33fa22557319be8448a3b2483ef3dbc1d187e39281e4c15593b2baf3827ef97904699a37ef16a8764c2f9f2cebe1a564d71472bed1453d783f7d8365fc6093b43b5a4cf394a8c26e42fd070335de437be416e08d707861893af264a159cf18898dc099a746c1ea44c5159847113fc5a1f4dcaf9e392fe79acd1b4a8219eabc867f60dd7f7c299cb62227624d93b7f545da50a60c165bf626fa95225d84ddb05396a6e94535c84134edc16a068320078fcc39521059ba851f3956ae99ef836ad8dcb78a187d16c1b37a977b281df4ba47a69b9b52bb646d2ad955ac14e25fe0c781a4339cc89b186aef6cccf29ecbd01d62152835057f4915ca317e5c97281c8422bddb908229a2c281544c22b3c99c5c030c3597f4135f46807e4825a828bc87f51d8609336dc08f2ecf4353e64c4e13f9c06044c8fc69d3f92e7e03a54cb6e722c332193212ba415ecd80279f25716bfbe7fe0394046372a167f2e02942a73ea23fbdd8b28e79d95d35ecafcf65e79f320939f4f55e90b183eb854a52a8ad6e18a8f5f2c08a59c796c6bdf34810b99bed06793001f74d9ca61c574f3c331edd103fa178162a8a8b43c65e2925b76746e5b576abca24ca1be6710637e7440307e8eb2ef952b9b5c1ab60b9db16a568e66f128c47d545c9141c4fa204149398b9c2c53b8b9bd1d9e053cc26926e6aee0b5a0163c1e3d320f011cfd536340b6d87a48dcc71d75dfe392e53322214131be970fbfd64bf2d023f9088af7e5354d5a0e6a8daa4c81eb9a82ac7365f07234abc2f626055e0b1955d767fe47ee85a3de95b1ae280ef33f9ab9d9c1a3cc7e9e79a2a7ae7ef5fb7215b6869e7c04f88ad21e6fc97c77ffdff16c4285f1aa8183f243bb776706508fb1abefca557ecec8c61540126da7ad6ff5d063582b8fabe2764be0fb8471ad1304b2d8a459acc9840b1a254a6ce30275b53b78189c22d17c55c949aa0e899d410c0eed95e5888bf674480c6b5b633ee1bcb330d6e30decee45e2bf8422cb2e9c3e758af511cacb26798019d35fb0c833a39d8f19d4e00007831a0ebf98eef020a7f22a422a512d4eb8f53067263207a6afcfdcf59890740413e136e4c5040b837c1d6242ac6dbbb20bd5fcb4c8f062a471bfc65bcb53a56c8467d2fbf46c7a16ac851968df1fb20c8765717dab86ce7c65509d9ae2594f1f0a8f9dd7a418cf9d3740328b65f79aa05c6ef257dfb4c258bda1da7bfa081d3ae1acd9ab31ce2c4f3006da2f31804f74f2ac6f94539831b20cb4be3fec2a7341a34c93c5d2080bc9d859f2d846c55c8a3e3d413abe3e89b87917bac32284665e21279165c49e697bcd801c76792d60e49a42bfc72dc912044e09d794bdf40265c08bc998302d2b99cc1663511a50c74acd3562fc429a11764c838d4ecb7e93d52f0cc405d47a56d1aba454452be5d3f82a44cce3fa528d89f04ac4cdf1634fa006f5eae28b53f045b06f45dc8379d5aec048855a72b995e24275011ee48b2c49a7968a01a5f8157e27636ec459543c0ada88e89e5adb2190ec5c78172be05d9687f43af5db3facba189c46596bf88b486aaf6e521070c76c08016ea482d7096dc52e2ab4101fa89d912e17fa5582fb8645b9c2967baa27a594bc95954b1b5835e8e20e65a8713527c3dbbe1fa459b4f870ff88a5d6bda16b5578801d8f20c3facf57e2d208cbfd170c6b8423a0f98844306620ec26e3be1bde177ede8a4aca1e039fd88056f86e85d236b03b763493e77e9424ba6fd521eb336c83e5d3eee114bab2d1cb9d403d571b5d50bd365f9a3cba8ac15d9e4c270f6e92f183c229426e1a718331348871783271f7fe26d148a1a997f0949a7616f9dc58dee05fee2d673b38065d4c677104f585d1794dcb6b39d2fa86389289d4d43ab588938a97b86876702d2e6b1580df0c446690a4b7317cae3fe526a6884e3b68b64cadcf41635dccf7d74aa32f35cce7dd725c675e79e85161dd232a20554af98726474a101f08bb9203ffd030b7ece9122512e8d12087c707a4cb49528c86494a69a282d89b7e23f3512200557f482eb7edef4b06ec1b76a4d65310c2fa515bc1e7f88353653834d97a4943303c26c29d5f3c6f59c54e90b9a41adb007c507465272beadb243bf6edc1c02ba21fac0a4a946c904e45c67e228dd1e1216a912a21c2d9575f3b14af8ac540c3f5af19abdf2df98f5cedf0cae5c2b45caf8ec8c19ff4818a55a8b31f703888a532f511107d206e47af5ad74fdc3c64b7c8ec22d5d383d8fc936e70c74db6de2d112417f0d8b0a6f2c4497ce35aa2e383af6c334d528b5e815e6311f9f032d37b40514b98034404d1b609b83870ddd9f5c6992d2cddeba3ab25b60ce5d408302a84ef982c6f29407933e12c6c4f36e318646a5e746af5fdb435ec4ab206ed995811d4bdc2cc75f4e949ee7c53d4b1149948ef3116afd9f4137bdea109a5a95a521a4db9b5a422e89732d25d4fddba22153e1b032284558e7bb6d7f2c500332743ed022b926594a30881bf6d1de02ccaf5487763be1243713a3e99e068c4132c6cd5811bcfec0d9878753da0c1cb9e198527e9475c10361ad9e0582c656eaf355cf59c2ae3008459e217f765edf9939493828d3b3c9c5b57cbe02b399c45efdf8ae5134d6f8068d2e94dca29200184856716a611dbf5acad55d6199868c0a2c3a5d986852b37aff5cc27d3fe54489ec79cde31338bd2ef3beed650ed9dffa365ee78b97793fa1902c4bd36f851d198ecfc905b5948dcc16c8e9fef5e135f3acc59d68988af15e48a21001e91bde533189be41cbb6f454853cdd5cbc9d42e1c91f865decc0015cf7c330777148276a9383cb37f466fd2b0c4a00806c3b22cfc57acf0a08137325d1f6f1d4833c6231d7df06773df301669a5f77be1d50e8d1332617a5bc39c2e2f27db111d8af4cf88be5cd44a1f6ed82989b8db35a9b639006ce6607cada97ce09feecbd6e3c52266bebdac8efbdeee756d05dc2f826e26bf4a79195d6a23a76b66e281ceb6703a8cf4403dea795095b7ea6a5d9cc47d351ff5f414889aebcabe0a523f89e69b5d099c1cdba7647d77bd9e48eb75eb7586a0822620baf72c29f0aa1b9d862f5d6547b8984bc1d2fdd677846e00536baa31bfcb1524e4215562b60a05965c1af187e3dca39a34518c47cc865495e5ebc3e139f5245a7eae350c45547400a33d64d3789af30f3426162ddfb344afc291cb6d6f19e15366742c29045dbc5c8dbb8220b468af3bd7d49e67b0afa723355c6f6cb8385792ec2bc6894362814ec695b19d97c8ca7f5ed71e99be1bc720d62c0c0e96ff9db3fe88bfdfa83586e567f174dd1186b2810c7c2ca1830f67dc895107d3ea8acae85d1b3e102aad9a91742dee2ead2ce92032a2b951966d7c478faa26416470c2906eb67183cad274703fc3146a6e80e52fc8cc4f92280625e45aa6b481b5f5826a4fb9ae63ce1834f775749998e34c7716531d5b522c2d9740783f124a02839fbe843e48bc0af8ca1f0d13adcda269544a5875f7eb7fa6893e2399566ff88367cb886d1afd8aa5008ccc34f5647a3c0fed6bd0c40910a328b93567a34f7576999e6c44f87c6cb341ead3349f70ee6d1f04faa131d7692980227b9ee04c99d4e120299524d6a792a6a661c7beb4f1438dffeca0991c52f306eb7d29bc98af21b72a19a51aca8d35989f65f77b36b9aaadaf6227178e32c7ebff2e0f2798519be476e4b31bbd2a85cdfa3189400de36245847d265735731aff6040be6474b573e9783193fe87f5183a07f23c65f6c0467ae9442bddfb2227539e425b7c6bf2e9c895ddcbc6d200e4de26ecb1fff3052f3ec0dd2770ac42b495091bf8e3d0747729584d4150157b50b162ab4ce1882a6ba828036f45d10d9e70894b1595565bbc6b7d19ed358089e3cc478a756a1bb7b9fd053ac17afc8e50fe7107f631e116d3dc737d92ca945d2786fcca984e1df60b9a4ce5c4eb1966b0caf8263c6a4a691209b40b9fc3dffb0cfdf50208fb5ff7a483b7536e9e0e3ca644e60bd78d472e93524d60c1f3a9ddcb5332b126ffa9f7e3651ed859b093fe1eca74f246727d3525114644f3495cc6fa631cd2c3fc9e242d3cd3e33de0f365a3c61b44687362d5ad57c00787310f15e9ebedac4f2cab5c4c92f6dd53f67abca8515814bed9b32e458b665b9c899d69e1f7bc86dd6e4134c5cb87455a2edc75ed76b33d56bb04b06a85dcd0c7100c9fbaf2a47e787543b324025b53e3e38d44378882114354c8c433ca353d3c8679b380b9fb71a6cb7d29517e60ef1a737d490363dc8f2c43853306031eaba9186b960f965748f9b6a05cd51bf9a00a010910af3f425f2de4c769c5bc2a181834decdfe56577022a6afe8097af0f6739b8ae8ebedaffe2ba50466aded2878b7f3d909b73d71d48333f0ec4c82a5e6a2aba5b0cd2f5842050fda539215e3fbf1e01911067ba0c6f54200305e51d4020946acef41e2bece222ffcc54228d5dc0c1309033bafedf65d4279363cda8683566fb09874badc37c7e40294ea6ad2c10a4559c2bda7fa1d512996c4551933fea73ad18bb53605f50c79fc32cddaaf35dc806724ef2cf1e869179f31a136e6df9cedaca42a21f372f3b98b31bcb7e477741e9e0f0d90a4a15784f10e36f9b6b8ae76814db93ec6a63fa647feaaa08746c3dc4d7c76c7045923655e604400c54cec678ca1d557ad4118f832c1cd20cb36914ad457ca17ee86d27072d7a2476368d8f42d5429093810fe9a9afb2c929684c6f34e3397e69c9a41c4f9b9eaa46fffa0a95b2086fe1eb0b71c812b09d173af49c0929f6bf21fe960c51171426cb8cb840ad4f9216cb936bba153e2d50942a923c3041f5fc8212c65ce8866859278c79cd652fa60ff129fded8d5dd72e8adc2fd69b2952ec798692a07e0f3565508caca8d3df6e5d4081397e5efbcef7ed6ae887ef7c30a72dd451cb0731ff2df2ee585e7fb33085437118c5929f51c6e58bb256d094c7ec6edd302ca78fbd20aa674de8ebec0d9372e35d9142116266358c48688980779f02818d34ed4d97a0fceac2b9776532423f8d7ce04d888666a0e59e490979642be3a7a370fd8a3bf05c41b145960c7d3ba2e7c131b08adc0a84b90052e847324cd185e74eb1709ff495a7bbefaba25c21d6f99ee79421d222bcbd8472b9c2ec9729626a36d582085a000648a73d21daeff088a332cfd3d25d142c5395e7aaafffcd5b81698612a8798d814ca18806a6a94316f3b935332dcf9f76233c09605fe13fe4cfd0ef0788016b17dfdc30581e346efb850425624490388f7eca581d2d3e45b7f43db8d6b082a4dfa02a3fd3443eebc65e38714b78b7ac9407bb050b2840bd06917c1295499801f4267ed39941d07f76cdf7f38932d0eb708ac801735bbff54d62063808516174b4a0a4d170759c7c59fd7b22cfcadd2377d7b44b7e5d34defeb852a2e73f5118543da9306a8978fd25ebbb406a0832b4b38bf1311837fdd7a23042df861229395e7086bd21f39761c045cdb628fcf1c6e0d23c298ea05b11cb101b79ce659621afd3bb0e5011550873ad129a474d7b5986087ecfe67367311610e337c1eaa4b4221773e5515313c9946479e73cf6c9cc7de740d432b42e7675d53fc9fecd73e60340e2ef9277db5bedefa3ebba669241f47913bbe0a09df82cc0b496d4c266f4be8532bbccd1bcc1eda06c3a6f320d7d2037b698d3bfe03784f517fe1b88e9971f823a7213f269d84ff9ed87cb6c055fc8b9e356dd2cb9610c3bf9b8fefe487d283211731dd1c4b48f6f86a125902699df14fc005c6e5ed764717483244d25eb181f3c51ce3a9e567f004a199f9f3de1514e5ad7f3746fd59532cc65fc3f4644d50bc4dd65491629b83076812711d636e184e961797e1cff14b35da77c724605dc2c7ceae028dcc435b65ced5f6409b2d450ca6cfad5b69f9f84a0f20994254334712ada0cb8411d2019a2a7c6fb69d06a6fcc22f2e68297926388d9b4292638a380a23ca4741cd0911a8a29f4d41c8e10580c79232c08555430fe9dde96cb05bcc6df1847166e4cff1d9d045406ff2c1a32576cc8c9536640241eb26a469c065c2d5d69091c5aeaeeaa94f98742310334a36f1d08fd66007381af0db15b840be1d1c48733a50a9aa7e9b8af235bc28e173ec29ccb3a53e6c02af98d4f1978db4dabb0c01856229600a28d0024c235cf4e7fc2d07a5c2c42dd762041896cf43423c9220549f9273c8918fbd0acd64d36e828fe0c3a988f6e8025b484f0a56194cd0deaf90f8be78c8cc1f01308a85ff7eac1b0fa2949b8b84dbc219f99144c85ca3ed132353347a91703bfc025d11d208fbb11f5f63b689389dafb02caa90a285d6cc025eb1406038320737e9bd2ed91629ed31719e37f33a5ae3f5bbeb84e00367d58d8e623ae39383704caf70cd3838ab52a97cd052cd32c2283d0b3a2177aa736a432aeb47384fe6b47fba797bb0fca529fafa0763b065c0fe2833a7450bf81adde3e4ec3c9b720071b14f7dbe462c71ac486cee22deb3f8efe7f26d49bf260955c0d927783d025ad5f4a2e75d519931f2dc3b450e2fb130ec78638dbb3c3b1958d9a91c84578997d99b747f2118c04080695f62da866ac360f31b8f41ad0b9e229d63c3f77064f32db6b57ed86d6a803d4db3f05451949ab537acd02b26cf8e1f9c768edc79a6a198bf50411dee2bc2ecf6a7cb907c7bc0870a25222b7bb1bc4d5d902eac49292d8f93290aec34f17d69d422c2a499756082462a902531b01eb424802d64ec343aa2cb7abbf9b6c398cf9d14b495bdfe245ae8ecaf459a4329ad0a42519c80f1d809d7436d80d196bba68815635021bf46d823cf8935dde1ce060a01605b468cb67ab1de7807371ebdf23b4d2d0459d1f29d54c28a8a49cc873dc956e2c387823f8fca30523f92cb1fac4ccaa2a9aad5aa8065962d4cf879369fd8fcb304026efb1493fc1f1fc2ca94a6c6e0dcd6088d45a7eb55b98cf29932a09c64a659e1fba7f81873a00fadaa39aedd7f46bb2312333ee84abac38660ea833eea231abe631016cdad665b5343c80efee1830b1dc2285f8f2eead8f53dc494993135ad80245bbb0e8989e44f29fda2e8c350eac7c13c7a1d65f49988acdb658a1d746ce902674be73b4882ca4a5dd32ff7248a7df15714d9db485dd656ab4eb033cba4f568904d2528707ab02b79af8afb13f11965bff05f0bdc0509efb20af3dd8c17552194e522dd9e5f48bb31f19c0340118a24b5d3ae2386134c5c4842c591a194b49de73224da0043d0822a0e54e0295c00e1a94240b3e183abda9d455bf266646d4621a46f626d827ef10938fee10744628a591c684a5af528ef68bca5d4ac7e1861b491fbfa76e03b8217fdba173ea3d89b20388dc8e48c263e3e394c5596de2be57b557f7acad4769f7e1203187a50963bcc7bec93f5766b13b68312366c7ddb0bc3bb8a933edaccf263b87b9cb4112eb85bb14920e6a30a100c769fdf387a15cc4fb912b0ef97d00db86abf60ecbc668b44fea657c8aaaeaa8db1a83c25e8fd952f115cad316d3cd01111e39970c39c3dd0e7945ac510e4dbeeb6c893560e55e05e56eb4f7dd212f7b3a0e3b50ba044a270dfaad14bd3fccaeb29fba7748ce94cccc8405fa042c0b01fe05cd0b5b8764141c3e1ca8b5c0640c694bffffd2941ccac730428c80cfae003a9104d97ce6d9d4735c6fe4daffe83d05c76054dd14f733e6b77b9fe845cd7fa6e01da3da685c1763081f5c9399f0affac6b67d937eb9894b5c1acb2c83fc82637e28e866856bd3069e370402c378c98f56d9f96fbccbd1f8beba7d8a6b9b5fd790aecd119273d2c6570313dd818895729fa50795524c74a60001887997fe4379a3a04357faaf396765c5bbb4c8c572291ff5a10f3ad1f67ee1d8e6a48ee035786ece16f69533bce316fe1a2676f67ca92853f191812ec228e96e506c1b14b965d49b7a1ea10beddfad9e76fff7f108b3eab6f56d0161d157d7ddd6a8e80d7e40a4d9ea1e6780414706dc968a60bac0b03941220f911c1114bd01c9f8567b844e8c0d7ea507141c5f9d5cefa2fb881ce40045fa265281ca529756c5ad75de01fdfa46d418f5f67aa37934130b136c91ad8ae4c54403d74898d8fc5068b5639c3b2d0c03045d161b72323959362b4bd1b41eb6c01deaa561f8060dcc05fbe5af1a76cc7027e525e8e5f6b08e6da81b36611d3cd5621255dec8b7e8bfd559070dae730c25b60b97d07d483d53264d640423b51d7a0c517d755681107307d7595125cb47fd61b885425debb37bb494588d620fe7893c26129ac9c94b1c18d07f24e48475eddf071cfd62d1987ab485547b03d8e46b965a92d86b763c52c8460e8cba6ef6b2dd2fad26cf01fca0a4bd6f5bde7ba84ba60a3d56aa998800c4e9ccc156c96a7bc417b8e65e605f8861822acfbed5a7ddf8db0735cb676a5bc492d63ad8bd92549ee222f70c7286dbf3654d786a261bcd46dd49ea699edbcc4eafa2f229d2ac7e2c542712ef53bf3961d31d08d4103b4aafbd6229f15fa30a03b5397316f7ea99eb7e7232bfe1203e9aa26f61329dab23d39ea3df4bb322646aa768f3b6c97382b3ca722e1febb830354bdc30f4fe239996654865f4082d812ea83276271fa8ae195c1294b3c140d1fe1614542a47c8be90468e71d2b8db6c94462f9076ec12713665539e6c9dbe571229cdb655152b1d4cb6511c7908fde7165b5a7d0e220a37d09d3661f84a4a159880bb4618efe9dd79ca7ec61319139e9e440bff3cd5af44ae489b2543ee2542c48ffe6cc8beff89bd39a1526518839d21907b177fa6c107fa475923fa93e864f6e01ed0c5623713f790c44e3fca80709b5a68006ab7d0591f2d426f9072cfe1e7b3238a9a4d26bfa29cf254fcab2145a3c7fcfd79ddbf92535d7d93db57fa26bc4f679697147f67f32872b53b38ec04b079ff4ae44e65ae3e90d490587dc360bc87ab3a1b713eb6f65b7de9b180ff81ab15e7e973d8a5cbd3f7f0df20c10729b715ac811dbfe273845f9b20cfca921b76eb83e11677c1e4124e2c2bc09bcbfea2f88b6225552c94677a8f03e5892e84a1ea9162353c42b67c2b5b05a2351f583b1c93a4cb12021779cad2a241e0139cafd832c19253af925f41a85340ce3ca761706c621e6cd1c3d11cb0aee8afb7200d4cc93df307ed213ea24ce9b90cd30d9c8ca3071390f61156df539201d3fe86e17da3fd9dde0499f2e4c1f96b81b06884692ea13d2434918cc6037ae04b94cba605d113f458d1b4dcb5376617c107f7328ffe83a8eb110f70c700c01c5c05412b62e5320ab01bc1d2fc4bb27e409330712dfeea90634d84769c158f2b1640bf1facdfd78e5416d616245a43e4e29ae300ce79301f7e7d74052f3c09d6984adcc8952e11e6dcef1aded7b82253bb1c2ba86998ebb7752234bdafe1c10413977b18814d800c817ded6b1ff1799a90c768dc9c68c8cea930160f05425b03f0dbbf329251a619b8984f6d0524e686b8817edd6aa8a38f1934f50020be62301ea154909f1afde10b34adb18951a7155a6d36238b43b6b2d31f0407fd8be59c4ebd061e7a7317a048a4353dfa4cf87df4aec5e9a858da94dc6cd8bc5a566183f8b2819213c2d01ef19553196cec46b0568aa1cdf0c6caacc4ca3ee74f4594e6bb92689e950afdca82cd19f7d2420ff3d6819f6dc8f2cd68874f563b801a34fa2ece1671112a7b32b7228cce16f08411e7f4130a5e627e62348a555f2cdd5de36c31d46ea1259faf07a026877a21f67ef9767583712cd1ff18f6b4389e7b701cb8c35eac699b621b37be5e4c43b3c79fca7022ae24e275a122882377b0e1614f0dd75afb6d57e5a5940aacb640e64f57b56170bbdb758e1e5de024dd1d3f4d4fef9fbaf39809b0e395195bb09d472442637c7a1e071e2e43922d6b53b0c6e1b9a20a2a8a1ae3e58fca2a19419ac458f8c1ecbbbcb659fdc5b841e80149aa7654f600f73926d020907bfd2684a6b6a0d9c5824a8f9698715b99dafea2ee3d901032c36715fb82af684263a20b057694854fd25de42b9a24c3f5b603b4fd6b6cdf79796bf2cabeaa1e53d15bb2f3f491d984881ba24cf170a01edf02a83e441284d3b667bf16052862052590b054bb3312c320bcca736d28ecb2062be52ca0d726eb8d36ccbe095768ed7a1137bc8ed9554adea4a6e0d4802c16442f054240280fe77f4e100483ec500e7594b9d4f424cba7bc0f6c9b5260afd0c946c841f0dbcd69978143ec8abfbd7a0a6362f31501f797fd9fdeaae14e60fcac8a72e92e75ad842a92174234bcaaa8069a46398c94cf2033e67c92228852ea759137aae4ffb715b716ec0551629b3d2b0b07489d36c85bb33437f3e2dff2e3e387e8580e7e854d2c9b8cdc629fb7574ab7d4c1bf0f133cec0ab0b7d9fa18cf899024a730d4f85b7ab76f0c4cba6c3f3984e767828e8fb9e3dbfde8c1f6d55b394424b3298cdc031835a89eb1d6c1c8a474a0c524f4d4da5c05e5b8e899be7b3e077756f5134b2aaf78b22866f29b693665fdec9ea99b0a63674719d0d36a9da4e5e8a6a45674c09744bd5071bfcbe220b2ff61836b360e277f5441a306da169950deb2e02ddab63717c6c1649057c86cbafb18584f4debf5deab7fcc572f54ee575d07a755877eb448dba238030b202adf6e8da6b6dc94100e29c7769c320393d9197131c1861ab8702769a07d5d970fd0c3cf32a77e1b7658b65a6c6bf39d73f7a2f1e9e80f3ffa2e54d9b8242d394fd0fedfd4666ad8bd83cb064d74e2d1cc033da1b2ff646d655fdff8f7b024394fc31b5846dc8ca2e24be340afe696d4b932ed4414d09aec4447cca51094099dfa925b65e8bdd6de732cc33a5177ed77949ee81393e12f8febae9b350d4275f44ad25e9e51eba8be78e27b1817cd0d563af37f8a5091f91d0cb2b6242808ca97620a04410cadbdc595c1291417b4735fa764c855f67b985675ba73bcd0c9207c346f2d006f3c582df857f286f01475fccbbd4ecb7bddafae1f61d0d32e7ee3896543fce35f12c8f2a12962792ec433ebcddca6f7e835df1c2b2fe56f6b168cbb416e3233d865be0d5bef5ba3fdf4d774987bd5bb3cb4db0d727c333504e0b1878bc8f32f63f2d140b049a94e38cae03f12fb955ff9c45ed081a7cdefadc8af80b12e75b227fc4d5f462fe9ee3274f4ff0ec6d1c0db79b13ef4d2a996c9971ff7a7bb9c156da8e5817ee6a832a6e0d0d9db12cd820fd8a656b66b3473e2152a1732326b4145e1be064abbeae6e241eeb27a7188cd3ad709c8ee8c61879f9915217f3b1206516b305ed3992cdfa6a0b9b8b7494b69ff462eb315a4a759318c0df409835fc69c9ae7a73828a32174f94329603a6b5321ab23f85d3a36932f8ae02aebd0a597088fe57ea334587c7aa0e117798218cca9cc384a72e1b52c346d36528694fdcb599695957e9128e54685588effddb8a6e2400f2291d4f2092bf0385d0922a7cc808713b6a76711c1b38393a547cd5c0742a91f39ac43acde3705dec7c0afdd839b972ba11317ca1048a7136baf77979456367f38909a48934b9e7bbfc0959fbcd2cd2254a846c0801b363814c636b7b9b08e4820e1626e78969f5fbe8d5f54873b9704f76b8f8f4d7fc551cd04610f1cad9af88700c4d84273f69739b9414e6664f1740945fead91cba271e361b56c290d50e18d3b29b84df90b7d5c9958ff807c3f872a9ae630ff59e420252ab1aa633873adf0cb1b2266d04ce5f9a72d4b1a6ddd32d52c2e1e1362cf95d95132e31392c84cd3f3b61aabec0bdc29d9d11e3b5c3344ae0c9667325287667e253480275c050446266146b77a8294287f036f58ec43363e64c421426a7d11c4c05e28a929961d9557d324966f67d38d87cc4b687826e1ecb745deea3848456fccccbc375d60a78c2672555f145ff6080b059fd1f0548193fd99c157b69c792a871875c2fb00b955266bacd45b5219213370485df9f47dbd4c8444f66df7a0b285682860d877ee5933ff2409ae0975e4f805cbc256c67ae8717a823238691bf40c5f4bd539518ccdf0715ebcec100533d297694ef06a53f1a29d67a58ef74094cbcf78e52fddff90d6ad17650ee268fa3b8f746491636b83be0cecbc7776afc6f9ce7d4f97fdc265aff79290bac532628821b888939773c331569aa74844d904715d6537e82d40aa271c18a9c586c22698dd8c05dce99b8effbb2c82dc6f170816bb123775c4b1637f667ebcbf51d5a1d44f63415d0c0c3243479275e06a6de157610c14c921da59b0bd53de834e2eeddaef98248121df2eaa333b1cd13e1d1c0ef0ba637e364d2243400690da4d91bb535a5bb2edc86fb6e8b121dbbe6e4f5e6a3bd0c30f46669ea4830ceb6b4ee556643af6296d56c7cd21276cea509e9d6f76cb67e9123520cded54565a338c0fb69e5de8324959d7d1e43ac9c704d6d8beeddc07ab67eda2f14b66cf6cf39ee1e0d9f751e53f5287c2807a88239551aa16075ff5539bb2ac4a90028b48026f834c897e149e459c165beb65c72f38979880be952a5752cbb36640b513235a63206914896f0c1bc80e24fa37b240175ec8a7fee8927065753126c0b06757383ace68ca84147fd8206a33f2224c6b084ca8941a08670281afd7a3d22fec176c8fefe02d76c2c4dc1dea3d6fbcd17d6028b41fbe128179687d1f7337f00a5ec3d78167062789bc3c0bfefeafbdccf8cc9a9aea3f34cdddd143ac44b05fa7f9ba533cf7ecd637e273c85e3816093f949cb617875bdefa97abc90f8f8d2257b6249828a1ed5c00636d128b5a7a9b56592f9561a4b80b65bf07aaa8460cefd5dbc09756dd89455c3c4823114b8716d54709a7329b3db871b5563a5ccc3a31e36ecbcb2de1897e1be50c9671091d1339bb391a2012d0702c29138efc0ce72b722ffdf5d05da1c6ddfb79b531033a649f58424d331326b9d5fc58465ad0e4300fe2b299e298e65dad783246c67e76cf151e0a35ceed41c119dbce622ec66f13294e5cd6048b77d8211a1f07b81135ae23e0394711e39b26e9db5e2a50e3f90027a66ee29ddeaee0ae3735b4fae13c9939f74b981dad143f21d08b901e83265fd7a1e8a9998c7acb540e1bc2a101f65a496f63d7e0d66106166fbd417a0fb03002fd7c80cad7f3b46dbd88668314b3a255fb60bc55e2be27312df1330e58c19937a981a181d96303f836bbbdd1941f089b8feeba1bd7f9798b02ace9ecf40f277443d776084f0ad0a5ec88f9ecf32345380690e744bce24fe54512bd0214595fec748bb8ba5720db434ae8a0c5bdbcc284cac3e91df747b4170f6bf1b3b812b4d8f1a2d12f5a812df5f191fe42801dc1454c04bdd20d7792fcaf00903462bfa68aa7610e2a7974b311b23cddd2aaee64475e4c4a337a1562999f3f3099ed8de087219e66e687c8bd9f150eb9973ebd7a302b47a55fa672a70fedbf9ee9ea34f9721fb0be22d2dd413ed8185979906067a768f4f9759b1ff4a3d90578cde159f8644117031f158862b21208f33669c58d47dfd9b7fda2bc4573b66bf34549234eabb52dc3ab74f867320e45b73b45a31a64656500fc5689d9bbfa1b40543e5e09ae72bf5ac88f06bc45ac908ae8c80dbd6b022f7bb1ccfe21dbb8098febaf44e5850a004d95e6a9f10e829b0e8db8dfa86330accf0a115068ff56aa5643e0252c166c6323ac024aea671ea2ad4df4b1ade3c7b15874466c331444300331fe2d1243266f9c4cc68f4cf386b1974b445aaab01a4ade0540d8351fb952c22ad25efc8d86e804546521c002806d882d787ced7cc1d74842ea59c26c9b71bbc8ad3bb7eb34a40c1a816b7aa867807783f45ae3f60dd93da2897f23dda847bef6d7e3750bdf26f1966bd3f7cc1caa1b320d032d5d642ce9748ae9385d4083fdb474548ae0df4605f159edea05a80f80b64428e587f017f760e5799bf4915baeb88f8cc4dced15e862f36a6d3269ccba397189f01bc9ed4634c31d17a5f6287c8bc6154f608d262010368bebbc38a29c36040a7b743a322c114c09c84e7b88fe48bde54fe71ef894803bbdf0b232460abcb6254fafe65d2767f5e657359ce8e0bcd2d1a47b232bf85e99c852fc8292d1a95b069a1578b995d4f530eb956de3e60fb272366c2d59dc17ca07e120249ddcea808952f7d2a5c084c880adf659ce8ebbe3be84cd2f7e9326d2811256bb5f84ccf2a2cd51d58a6bd0822cce374db962c74f277463be9f47839ba8710450732f0ace310d05568f86723aafa2c9fd05cc9d0a9e79a30563e447332320c65a3a1efb110226481811e2a196f180be10a4a60ea10768f051252d5a029db6dac66319c361e1eb1fce38e0d3a1166ff00124fd6a5f803ce51884f3b66c4d01a4a7ed536ed04f3ef9cc7741ba19fabf2e0bd86088f45c000a3d513f3617638fe60f8d5897ad27cf4ddbe012382c71a9a8edbc1507e00a4585686b10f333859cd9cba038511fd53d2636ba3a772b69a21b2f9e4f94f3a6be818ff7a0506131e56ad92ac6fc164a3b6139fef44737cf8d0d952ff4723d42333650c1d974b93264e6006f36b4389953501f43ec969011c04d54271bd7ee93c341d7ae662f6120f293a6b3cb32d5b71df25950da3b25a01b332a18a5be91cc60bdf3a1ec63c97fc4d026b19f1e75a5e1ee18e064f44b45ce5bbdc6a33796d7671e1996e724d2c614cfd3aa65b7b035687cce919ac7e228ca7a738f726b53db7cb758a7ec024d12ccab796cd97d24ed614435bcd673a10053c53cc2e909919c08499e06a48a9c694a6fb91207f447c9a0b09a99f90c6c07f7159b389e663c728787eadd045930fa00295b20833e77a7ae797ac82d1ba6e5a422cf70bf1f973c3ad44f5ae5d51cf2221cb4e082266130bc901e3dc4c402979d7bf59b4386d6ad107ab1c3c631a1890a98b612b676767823f682902ba7ab7dc6326680be99c0d4f5deef7a58695956d64c3bd83e30b80f11945908296576c98775271f32e14e8aed6ad0ee2c5109665ee91e8382a42d67f55702480a00ba4257cccd975f78bc97806be91ab68d58df979fbd148dcf49f33318c7edbec357cf3ed3d778ed377833aa5136e94b01c7ac9a3183542d3815fa9b5eb74223396abe87c9fba15ef5bea8e17bf464bb719b7c1447f373ae7f227886c0c6df72c102b25d346187055d6bb232861fbb1fe7c8b5699a468f66db8b9c3ea4497c7fad992c0f93e25c27a27164091bb2fd1e93e05a48388f1a6f6dca12435ffe315fdbcd7ddbc68f8b84256b90810ebd2e1b9d5fbf248f69a337571750e4fc0f0ccaf621e3c1cf6b80985c5ff7026c0ac6fa017b4a933d6dc77f08b1de01a58ac5e52176497f814d3d54972f6c697f30d5078789b09a22338fb4eb1fe52acefa7212fef68383a78a45828dfc40ab5f41fe376fca39d65a06f94991381623d5236a41276700275af839f6970658417365f799a0c941be7ea9b7851fa86da01a8e4c8005f7086fdc9a96f661013dd35d171220863994a3633d9d35b256d43e168a71ed475b197a774a505ce3683f0721c7997482be4b8f19805874b73c367bcdcacd7d21db796a980e05b558f1ce75daaeffe091874caa0809c130689208ccea3597fcf193f3a7319143064ba9663fddf32c544f0243ab262bb2031ba6134e16da823db52bdf1060b37df1dbf53a370e777760dd01885475a05b9ed3131df2d170164c5238c76331cde53a701425e12d3aaf1ed7024f3fdcbc30cf8aa4ae71b8beccc31c789f0441a0b876e177339c742f88423ff5afcaa8ea354d9cbc644ec898af3e31806f1197125dea32afcb8f14da4e5d0328817093384fc2019de2a83c20b44ca843de9435733944a23050f8779ed131049077819047a579e42d86702565ae2fe31c1a564994b84398ae42912f55be0f92328aa569eb0f2b2d0399c0a7a3803af4f18cc9685a61b7dc381ac990801b10ebd2aa2faf99b3b1d9c6eb37f3d73ba064ff36f20a19c71a06fb29153fd0b7047e6fe8a43ac7887993e50dece34546c488f31135a4173bfa1e39ca36afbe9eb7407e9db5355d28c30ff660c8ed192e36868d9a2d4d8f65eef5a4c4f72d8816f1f8a5212ed96421e1a53d75f7f6f2d19c94f5682dc986f733c4460eaddd394317edd43ecbd2ee45f64ceb5e4f7e580b8a0d5ad764c742e05080e3aba62c64139f1b102ec7f78fe063cc8803df990aa5567a6bc63e2d5e5b0bc7ac112287ecca8786267d45cf84a4eb98eb21ce8f9716b2edb692b69a89c053f5c5e4f7d6691c9b1735cdcbd375d58395a725191710e89c04c8766e82b4a9054c65ae068707831c1e56589e8a235deb8b8ed170c55aadca0273b180867bdc384ba1302362ee9010ef58518ca897f9bef0a8819d2efd9d320bff83d1c41d1ccf449b5d917acbeb7691c92bee81d5b010f9de7a4a8efcfc777932c785be1003558db2139fe503300699bc8ecd03493928f7eba52c8159cbd219dcb23093f373d588f7caf4432efea2f6e5d3a5be6a635cd1610ffdead7e60ef9ff0c3f4da96fffcc93a2e90b36b5e38a137ef06cbccc4ca5607dce3890ce5786a3e8347f4389a82c567e04fec0c31c930d553219c88aa4562a8b628acfcf9f465b29da04d5086f5a3bbc7dd31734de894f455ca6da6a71ced003457150f11ea2fb4a6b541e0fe0e64eeca0cbe15699d28b7d7c06817ee9e16aa1fdb26019f5dd94ff58f9c5a7fe2b0e4d4506504a05df94a452af21e759660c67556b05762aafe6b447a5d0414a66dcc2bbda2610662b295fa689d2cc43afb02ac1e921b69b3ebcb48738918ded3d3a03b012ff14b08b4835e33a282502e0392c640024712061c89b074defade502d3ac7df45efc22d19c80b24586af31adc532eb72e4808f0918e9b4581bb59a23f28a41eb5c951c93c33174de3958ed214023b8df185ce5c4563a1496b4902a3dd990d95e9c0b51aecca4bb8caa35359c6e8a69405d9538a5ff6f77149eda4a650286e94062492d422d6264e86c55db9a6e351fa579b6949001f96419eca4da5d3cd68366f12fb3ffcc59cc11f7bc151e002d7cc37cb91ccea895e2324613de93612713cd5032c5ad960abd9baaf645c63de994ff275d2d3c18723eec92060807feb3a7fe130749d2e7e92cc728408a76304dc0be3d9146208e15716e7592470252749060b1884258e5095d5c57a2d4d1d7367a6c5f2baad4bcebc475df17d86b81007472f82cb29fe8a46ebb30466f44bdc7dba6ed40921972103a6463b99fbb91dcc1f3343b5b2d29e940868f5b23a526cb242e21e97c127811725f5f96e95ec0b4609299de93a52ee7e0e22f4fe8c852b6316670a103b24a12acc94ff0346576b719ab22c4e9ce22653ce0de20fcfee6792a01706d6e4efc2687a4658d19c6a4841242f255b761256a4307d7182742827da177f7aac8ad3ea6aff893427f7afbcec698250354909b465bdfaa37541cfe839842e69d658842f77fb70e3c99e590d216b54f0c17813088dc02215077ba1a2635e357f1306991280619ad245bc4bf1e9a23124e70dd494509d7dbb5df261162b0f05bcf9d2eec4b76a4c6fec2276f4c3f6b6bd61fa2ea911e98f76660891c6ced8f9e14706cc529d9a6a93eee9d8b3b7e3157c3b691584515151a3c45e4bd7c7658ad5fce87c3bb8b4f5ebd118115798803e1e2e7ee93df1462482206fba4e54047591ed3dbbfb050932ac7aa51bee9940d4dc678696ff121aec0a43cf1ffd50a01c94164f5ce987262d011dfa4ca81e450dcf86db55574a5d08398ce627125915b7ff51b73adb6cf40627bca559d80e30b55688698a5c4d6ec8da677c0e40b7d4848e10296a11d56f4945d8762121b62d3fb28c15bd8c3a0c75cc91400c437e914c9547c86f47e07ac14ec0c8d4698ac1635ff13dff84e88ca458ac7f2270e6a4909b52c220f8275fe17fe16589748c78ec4ef3800d2c475a7ef742da445de2344515937fa27af03ca2c9d881512cdf6a9f6bd1e0df48745e5231381546c956ba123e79ff21ea486e4e80f8586650252007b3a20f10af99e89cffe58457facc073d3678f9ab31c18457cef2b741883897b97bc192a3b234ec1690e9888db06975c130bb71c69e88c50fc0ff3b5e6636bdc5a66752acb6602444c062ef0c17d3ccc7cc6ba0d17069f1733db71802cc10a8d2967e3be4b41b6bd4be093d7eee7f3d614d45359e83f425fd2431ef0d8e8081d87bb571c46f27ac5c048b31cb472c9de03bac99e4018504ab9079d14f40888131111d3fd0673e433b3d80bc8158ae2fcc6dbd31a187f54279d440e3aaff37ca4043dbef5e6fc224c1c094eaf5bfa7cae513b7a5c45b72a26f54609b13240f72200aeb616a30d5a5c1dfab6aceb3c5bb769cef69844fcbe112b04e679240be38f380a0b1cda817d0f6237c4930345a9f2080e406633d6d65fa7bfb2a07ae0c8315d0ebecb33f7724ceed0dbe407650fe1aa910c882a2720cd7dfe6e7d36ed4314f244290908fa2f1b2132bdc6192045d6971ee5b4a9169012a0417c680abde1b09e05104d998b60c56c1119a6853098944b84bdf19f13256356584ed1be0c676cfd1dc6671961d52a8d2b7bfd9817c7996761fd16f61f86679eb8426b68780a4463777479e8021603f1177c90a5e0d19403044b94a81c29e716442941b3495ddcbd0a667eeee7b06e1c0cf4b3408b2d8bc65b14d784c8dfc97d1ac35b04a5b4c1afe36451b8db20603e1bbb49985b88f41cf5d9d6a196edd46964a2e206c57e0bf4b8532d85b3ea180f4fe029b9e488f94a11ade96fcfd58966b5f9857ae9e00466c82c10249fd0e0ac974455bf3986e8c416ccb441df16e4ee2edbfcd19c668d63c59b36e9936a7000a3f18774cf0860456ee3d6b9a50fe3d11b32d294cfd70d209900aed19bc24a752e58ddd9438faf12a74aae696f03a28cb4be12b3b8a9a50fa2065919d48093faf82a318ed19bc4d9889b024e661ad2f1f5b161390eca72f68d37be352801ebb35c703d70c4f0537a5489c71b109e4813daf92166ceb748f859a523e3eef76a0346dbf8ed28db780f4cbbeebd49b0f10ff89e3d941a6b98b59516e9406deab32f06709d1e837f56b1a75bf68bc9604082e1acbee65cb8d9e2ca2ef5557b7a5602b16f936f64ed813533f3f2417f71792fea58da1027c16ce47b338bff336686374b4459ae1f70086ecaa046a28b4db2cbd16c5c71c9ef875f301d7565901278ac72d6014897466dee369aecf210afa8bfdec2445363b0a47b9e6b77873b2e0815bd4faf27fe7d8bca78b240c6676657fc719621f4a2e57d1b688633c20865a175cf2d916b98e76d29dcac087b94671c11ec370d435ab4fa1da1279c8eef2da957ec020de45bb8f0f520897eaa70cee9eecf8e3d73f5eb06ed5e3d7ed63853548d2ee7a4cf0396bd5f4c23c120c85b6dacc16e008f17fc4fe131268b05df676bd8f750ab3ebb4cc905946bede2bffd361aa324154fe10625bdd57b14a93a1b01649d7afb79b1c4a55260de9ac3a6d7a890ffdc4adb995c35965cd45f66363c391d135c250e7a46b1ff8744776acc1650c376a05b7ac16ecda3020b0f6d2a67cfd970da141b2cbadbfc5facd7ff3c5042a57c8ec7aed6e45981cd37e6e5c8cfd7d11a541ba9feaf366ac0d6f68424c94b45e2e7fcd3fd55cdc55e58bdc3819a0134759bbfaf8c6c9a9932ac1130a9789e9a11abb02a5300fd5ef8907035773170ea7f29b4f791d3cd88115374cfe755c37ffe030281f1ae05432064c630ebfb46106b6aaf879b9a07a80d6fe3a98c10311b384dfdbab96190889e58be6a9c978b3b683f7b1384a28cb719a1e537da0e73de265a3989884841496c408ad720b1077aa89439f893a404442d5dfe95759ff1128801915acda77b4cae2e80039f093516b38e55092089ae887d6f031994f80a56b2f31e9d0e3ca4eab26789a52269db0c70ef43053a9a20f3c00b664644b8746bed291fd28d6b8c324e8ac2ab9a7b074942dfc0910c9ffbaf2d0d2329a10d4fff012d53cd197204b7c171f5279213cb85f98f55468ae075706c0144c860fcdec20e3b0174f5fc3bd34f5d453f672f5480e9cce0013e17dda10f4dd3efe29bf0b59876b7315b64e6b07bb08834027b1fd9e5fcf57013932b06192e845ca1fac42fc2b3aab9fb42c0f7357beae42a5ea67bf25fbcde300fd95ad90d7febd0b85d5287369a1fde5189af880328ba2b2d78c48e7110686c8ea4974d0debadc9b905234c22c298d76f767d58f08146c3854c6775c7a2dfdc57b9c72776f4dfc63d4b39442c7d28643ec8261112dc6022f9d39bbb1a6ded37793357ef86da87e4d9c3fb6e0e37b5048c46a6cb08aa1f578eef3314742e0530f20f55f5ecc8806e9fba329c3e493ba4f8b742aa9076c305a07ca906ee2c267785558325f28b0a5442c371496f34c226c8fa8aed6473731bf95f75db84aad908b9da733c242b534f3689c271f91889049e09b9d3f9ff1092c7a68bc3d26e5406bfd6796743ba836a50b89fe11cc718d6438777f5be579004b84736dce19497a30e4ddc619644328d756be7b67baffa783c65a5a736b367ae819b0f5d4ff560c80391b96d20f467d5b672e80f7ac96425753580db31b44c607f29fb9ade196df517271a5fcc898edd1bb1d9cb454674e58b8d0b086494e9d304322ff3644d5473f95ed3e57ed112558f9927ce0ee41a047ac136cd29f8201b18d9b442bef9de62196f5937aee496a0026df944bcf94f86b9f168f3369b1b532aa92ed410ba8fcff542c4f54cb0b3ea43478730ba8c95bf63a224247b9f98b21af7ab51d290be6992a1bbf5165c315dad12f5f63a6179318ce40c3c98cb8f43b46756e6942b945779a813b3039952557e11b7d237d794599c2e0aa83910a7bcde00178041d7061a4f9622d620796167f44cec6bc9eadf8c148de6e216b26e2ec367882c9269f6ce7db8fd1751f863c9322287ad6412622c65073500e2d3209b171d9d4959751d8b16f63ebd3c64aa6d9f0ddc7c21d8650ffdf21bbb46563b2bb7551b8905e867e0db291c5af32c8b27a96ef42e0d9d0e37f1a5da22c9fad2a2bfbbf3932313cca658d50079c64c18847b8dd7fbeca4c8213b90f6f97c5be9fb8d3ce0b182195df2e6d6100be722f30dbd05af9a2e6b50fe8718c209d9133f9b62cb9e6c46774d9c197903e3ad5b336b96842929bfb3b58445c87275d732262ab145c243279ece8ce849b59f60fce20cf3968e3d5517d44f49ed4b074e9d485a625db366fc9a520b84bf4b92eb01ce2a1b7a8e618be78e9c797b31927cda9c18a438c055eb662f95204772fd46c22f569c96133908f51a0ffa43211751b347594156729deb91bf9be1f3d28f09379c6ee106cc6483fa641b6b3659954d548b4304d508d320ac73ced37c64c4155cebdcd300004c0803e4305366c1c2d6fea37d48df4f2b766162078dc7b9d3aefa2f8cbd03b53515ffb6bec96f5cca9a93df1723c1d05024cd25c6bb8e48bd70d37e8bc68cb0b564e2b6c1efe3ae7fb174a4477e5fe53fe4f333f0ee3b4a692537238dc7fac4ea3e308e586767034e3d3279815160a93d9e3f02908adae636daf62e6295353fbc972cdac00caa162ccc09ca1061817aa5e035d1d8a76ac5b309fa2c642e4c420c2db82c169d5d1b5f47ac795b8994dfa93e35799b3296340b29b6f11f5d318360d45c559b2987488aefb2f06e26f9d1b3b435f6874e3f6021e9a6c4aa058b6c0ae78d5dd3586e3498be80d7356a2fcfd69f3a422db2862fb57e5cb484f676b14377de41890b83784fb6f84528ed027a3244eab8f80ab8f24af47cbe35687abe81e94349cfc5fafa00c467e618192ae7b0431d3663454f2c9f11c0e332a4a177f82c17bc5c87fe1598552f7b22f203156056d80f3107b97238ad70f38e18c7b302d77519a806bd69512c4054359387595920497a650705547f23b1947232066fc6367265de495fc91523b2658d6b1fe5c734b054de6fc9b0cee6a0ff94bc54bb371eddb71540ed3ad53709612f71b95faa4fe9a0fcafead83cc07cf544af6d7c07c71c01f900ea003abdcbc71470cfb3976ac26d88e5765d9c8b90944c65874b97990cbc1ba57f646c2f1b899338723a91ee74ba36e82f2ff31ce6fc03c8448c84331e8300dc90ba27a29dc7d8c4aea9781dc27bb9991df3c6dd624c85166d3c8b5ed92bf6997d919c2c9e7c187feb8e65ba7e2cde7bec3fc110e9e8053c4132931b352d718ec20f7c601cc602c411797868d4f6561157e7a8c255f8a2c509b1eb2ee5ea4513b463468560dc50c3945c1cbedf351d2185154b486629c7e1105ee7c8b9b4d43e7522dd877c271cbe07e1ba09f91e08dfb6ad02d1cde92b5e8ac444c34cb592e9cbb3530fcd3db4e416f37dc1d6e533a551f64b87151d3b6afebae32f061f0fcc4188619de55b645f1e2e7416db5698557b827140686e5a2b9cb8511b3cc43ad40b1c23861226a564a2ae493778c102c0103a274bc670d759252cc74d15107baa9885f1cfb32e3224f9e00bc2e8f66836227212e195fbe5d0ac8fc7a446ea1e3a367f6751e3554f642c443a98d37e0ec648219015416ecfb308498f5f892f98633ff899799630b1ab5efa54946ef86e4dccf5adac0be07ec61038eb9249b28a40ea810ee91e75b6699f3543db060d79d275ae94e3a79d90a8159ad5249d9c43cd3fe7160fbb03617e0e2d67a3b04e0b3f0c83ed4de789a31b9ec713f697555a2ef69c2c5ab2ee55acefeb67b95ae1bd684d32f54938ca1bdf7a6c5b161b4f320ee41be8b02b9e8724cd70ce13f8975e4e33e01e303c68bbc28950570058fd61eddbbd9cba3e76f9ca22474d964c98826ab329a8e5ee45b7e00d076b3aacb53c4a0825a9bb46333c9fb74a247bf4ea79c10c1bbdef62d1109f22f4a5985222715cb0f9771b96ab4d75e18faf2db24f468c485e3ca80df9b770c2031fa02b92b3e0482fdb8b9f1ee783e7c2892a6966f295ea8ba03100c7f685a2727752a602b73ed510b374ef6c835829865c7a30ad1bcac7ad495590567e5a7dcadd43ba70fee1151578a3940aecefe822310f64bb9ffcece9fd5466b260f670cf3e94b4b1fe53703a30effbfe88564fecdf6493565e2f9b29f712aac6665cad2d61d59fc5704c1</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">输入密码123</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css"><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/02/02/Java%E3%80%81JVM%E3%80%81%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6/</id>
    <link href="https://www.sekyoro.top/2026/02/02/Java%E3%80%81JVM%E3%80%81%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6/"/>
    <published>2026-02-02T06:30:36.000Z</published>
    <summary>Java进阶知识</summary>
    <title>Java、JVM、并发进阶</title>
    <updated>2026-06-09T07:12:11.747Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="算法" scheme="https://www.sekyoro.top/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>再次总结经典算法题目。<br><span id="more"></span></p><h1 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h1><p>一个程序的运行时间主要和两个因素有关：</p><p>1.执行每条语句的耗时。2.执行每条语句的频率。前者取决于硬件，后者取决于算法本身和程序的输入。在相同的硬件环境下，不同算法的执行时间只取决于语句的执行频率，因此可以将对执行时间的关注进一步简化为对执行频率的关注。</p><p><strong>定义</strong>：$T(n) = O(f(n))$ 表示当 $n$ 趋于无穷大时，$T(n)$ 的增长率不超过 $f(n)$。</p><div class="table-container"><table><thead><tr><th><strong>复杂度</strong></th><th><strong>名称</strong></th><th><strong>形象描述</strong></th><th><strong>典型案例</strong></th></tr></thead><tbody><tr><td><strong>$O(1)$</strong></td><td>常数阶</td><td>无论数据多少，一瞬间完成</td><td>数组下标取值、哈希表单次查询</td></tr><tr><td><strong>$O(\log n)$</strong></td><td>对数阶</td><td>每走一步，范围缩小一半</td><td>二分查找（Binary Search）</td></tr><tr><td><strong>$O(n)$</strong></td><td>线性阶</td><td>每一个都要过一遍</td><td>简单遍历、求最大值</td></tr><tr><td><strong>$O(n \log n)$</strong></td><td>线性对数阶</td><td>比较高效的排序</td><td>快速排序、归并排序、堆排序</td></tr><tr><td><strong>$O(n^2)$</strong></td><td>平方阶</td><td>每个人都要跟所有人打个招呼</td><td>冒泡排序、插入排序、嵌套循环</td></tr><tr><td><strong>$O(2^n)$</strong></td><td>指数阶</td><td>噩梦，数据多一点点就炸了</td><td>暴力解斐波那契、汉诺塔</td></tr><tr><td><strong>$O(n!)$</strong></td><td>阶乘阶</td><td>宇宙爆炸级别</td><td>全排列问题（旅行商问题暴力解）</td></tr></tbody></table></div><h2 id="递归算法的复杂度分析"><a href="#递归算法的复杂度分析" class="headerlink" title="递归算法的复杂度分析"></a>递归算法的复杂度分析</h2><p>总时间 = 递归调用的总次数 $\times$ 每次调用产生的额外工作量</p><p>当你看不清逻辑时，就把递归画成一棵树。每一层代表递归的一级，每个节点代表一次函数调用。</p><p>例子：斐波那契数列（暴力递归）</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">fib</span><span class="params">(n)</span></span> = <span class="built_in">fib</span>(n-<span class="number">1</span>) + <span class="built_in">fib</span>(n-<span class="number">2</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>树的深度</strong>：$n$。</li><li><strong>每一层的节点数</strong>：每个节点分出 2 个子节点，第 $k$ 层就有 $2^k$ 个节点。</li><li><strong>总节点数</strong>：$1 + 2 + 4 + … + 2^{n-1} \approx 2^n$。</li><li><strong>结论</strong>：复杂度是 $O(2^n)$。这是指数级的，数据稍微大一点（比如 $n=50$）电脑就跑不动了。</li></ul><p>例子：归并排序 (Merge Sort)</p><ol><li>把数组对半切开：$2 \times T(n/2)$</li><li>合并两个有序数组：$O(n)$</li></ol><ul><li><strong>树的深度</strong>：$\log n$（因为每次都砍一半，砍 $\log n$ 次就剩 1 个了）。</li><li><strong>每一层的工作量</strong>：每一层的所有节点合并起来的总长度永远是 $n$，所以每一层的工作量都是 $O(n)$。</li><li><strong>结论</strong>：$O(n \times \log n)$。</li></ul><p>如果你面对的是类似“分治法”的递归（规模按比例缩小），可以直接套用<strong>主定理</strong>。它的标准形式是：</p><script type="math/tex; mode=display">T(n) = aT(n/b) + f(n)</script><ul><li>$a$：产生的子问题个数。</li><li>$n/b$：每个子问题的规模（把原问题缩小的倍数）。</li><li>$f(n)$：递归之外要做的工作（比如合并结果的花费）。</li></ul><p>快速判断口诀：</p><ol><li><strong>如果 $f(n)$ 很大</strong>（合并比递归慢）：复杂度就是 $O(f(n))$。</li><li><strong>如果 $a$ 很大</strong>（分裂出的子问题太多）：复杂度就是 $O(n^{\log_b a})$。</li><li><strong>如果两边差不多</strong>（比如归并排序）：复杂度就是 $O(f(n) \cdot \log n)$。</li></ol><p>递归不仅耗时间，还非常耗<strong>内存</strong>。<strong>递归的空间复杂度 = 递归调用的最大深度 $\times$ 每次调用产生的辅助空间</strong>即使你的递归里没开数组，每次函数调用都会在 <strong>系统栈（Stack）</strong> 里压入一个“栈帧”（保存局部变量、返回地址等）。</p><h1 id="数学相关"><a href="#数学相关" class="headerlink" title="数学相关"></a>数学相关</h1><h2 id="公倍数与公因数"><a href="#公倍数与公因数" class="headerlink" title="公倍数与公因数"></a>公倍数与公因数</h2><p>最大公因数gcd</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">gcd</span><span class="params">(<span class="type">int</span> a,<span class="type">int</span> b)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> b==<span class="number">0</span>?a:<span class="built_in">gcd</span>(b,a%b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">gcd</span><span class="params">(<span class="type">int</span> a,<span class="type">int</span> b)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(b!=<span class="number">0</span>) &#123;</span><br><span class="line"><span class="type">int</span> tmp = a;</span><br><span class="line">        a = b;</span><br><span class="line">        b = tmp%b;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">lcm</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">return</span> a * b / <span class="built_in">gcd</span>(a, b); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>利用<code>辗转相除法</code>，可以很方便地求得两个数的最大公因数（greatest common divisor，GCD）；将两个数相乘再除以最大公因数即可得到最小公倍数（least common multiple, LCM）。</p><p>也可以通过<code>扩展欧几里得算法</code>（extended gcd）在求得 a 和 b 最大公因数的同时，也得到它们的系数 x 和 y，从而使 ax + by = gcd(a, b)</p><p>普通欧几里得算法只能帮你算出 $a$ 和 $b$ 的<strong>最大公约数（GCD）</strong>，而扩展欧几里得算法不仅能算出 GCD，还能帮你找到一对整数 $x, y$，使得它们满足<strong>贝祖等式（Bézout’s identity）</strong>：</p><script type="math/tex; mode=display">ax + by = \text{gcd}(a, b)</script><h3 id="推导"><a href="#推导" class="headerlink" title="推导"></a>推导</h3><p>普通 GCD 的递归项是 $\text{gcd}(a, b) = \text{gcd}(b, a \pmod b)$。</p><p>假设我们已经通过递归，找到了下一层状态 $b$ 和 $a \pmod b$ 的解 $x’$ 和 $y’$，即：</p><script type="math/tex; mode=display">b \cdot x' + (a \pmod b) \cdot y' = \text{gcd}(a, b)</script><p>因为 $a \pmod b = a - \lfloor a/b \rfloor \cdot b$，代入上式：</p><script type="math/tex; mode=display">b \cdot x' + (a - \lfloor a/b \rfloor \cdot b) \cdot y' = \text{gcd}(a, b)</script><p>整理一下，把含有 $a$ 和 $b$ 的项分开：</p><script type="math/tex; mode=display">a \cdot y' + b \cdot (x' - \lfloor a/b \rfloor \cdot y') = \text{gcd}(a, b)</script><p>对比最原始的等式 $ax + by = \text{gcd}(a, b)$，我们可以直接得出 $x$ 和 $y$ 的变换规律：</p><ul><li><strong>$x = y’$</strong></li><li><strong>$y = x’ - \lfloor a/b \rfloor \cdot y’$</strong></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">exgcd</span><span class="params">(<span class="type">long</span> <span class="type">long</span> a, <span class="type">long</span> <span class="type">long</span> b, <span class="type">long</span> <span class="type">long</span> &amp;x, <span class="type">long</span> <span class="type">long</span> &amp;y)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (b == <span class="number">0</span>) &#123;</span><br><span class="line">        x = <span class="number">1</span>;</span><br><span class="line">        y = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> a; <span class="comment">// 返回的是 gcd(a, b)</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> d = <span class="built_in">exgcd</span>(b, a % b, y, x); <span class="comment">// 这里的传参顺序有玄机，利用 y 接收 x&#x27;</span></span><br><span class="line">    y -= (a / b) * x; <span class="comment">// 此时 x 已经是 y&#x27;，y 已经是 x&#x27;，套用公式进行更新</span></span><br><span class="line">    <span class="keyword">return</span> d;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>EXGCD 并不是为了算 GCD 凑热闹的，它的主要应用在以下三个领域：</p><p>A. 求解模反元素（Modular Inverse）</p><p>如果你需要计算 $(a / b) \pmod m$，由于取模运算不支持直接除法，你需要找到 $b$ 的逆元 $x$，满足 $bx \equiv 1 \pmod m$。</p><p>这等价于解方程：$bx + my = 1$。</p><p><strong>只有当 $\text{gcd}(b, m) = 1$ 时，逆元才存在。</strong></p><p>B. 求解线性同余方程</p><p>求解 $ax \equiv c \pmod m$。</p><p>这可以转化为 $ax + my = c$。只要 $c$ 是 $\text{gcd}(a, m)$ 的倍数，方程就有解。</p><p>C. 中国剩余定理 (CRT)</p><p>在处理多个同余方程组时，EXGCD 是合并方程的核心工具。</p><blockquote><p>求模和取余在有负数时表现不同，对于a%b和a mod b</p><p><strong><code>%</code> (取余 - Remainder)</strong>：<strong>左边</strong>是老板。结果的符号跟被除数（左边的 $a$）一致。</p><p><strong><code>mod</code> (取模 - Modulo)</strong>：<strong>右边</strong>是老板。结果的符号跟除数（右边的 $b$）一致。</p><p>数学本质：向 0 靠拢 vs 向负无穷靠拢</p><p>之所以符号不同，是因为它们在处理 $a / b$ 的商（quotient）时，取整的方向不同。<strong>取余 (%)</strong>：使用 <strong>截断取整（Truncate）</strong>。往 $0$ 的方向靠。<strong>取模 (mod)</strong>：使用 <strong>地板取整（Floor）</strong>。往负无穷的方向靠（向下取整）。</p></blockquote><h2 id="质数"><a href="#质数" class="headerlink" title="质数"></a>质数</h2><p>质数又称素数，指的是指在大于 1 的自然数中，除了 1 和它本身以外不再有其他因数的自然数。值得注意的是，<strong>每一个数都可以分解成质数的乘积</strong>。</p><h3 id="判断是否是质数"><a href="#判断是否是质数" class="headerlink" title="判断是否是质数"></a>判断是否是质数</h3><ol><li>基础方案：平方根试除法 ($O(\sqrt{n})$)</li></ol><p>这是最直观的方法。核心逻辑是：如果 $n$ 有一个大于 $\sqrt{n}$ 的因数，那么它必然有一个对应的因数小于 $\sqrt{n}$。</p><ul><li><strong>逻辑</strong>：从 $2$ 遍历到 $\sqrt{n}$，只要能整除就不是质数。</li><li><strong>注意</strong>：$1$ 不是质数，$2$ 是最小的质数。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isPrime</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i =<span class="number">2</span>;i*i&lt;=n;i++) &#123;</span><br><span class="line"><span class="keyword">if</span>(n%i == <span class="number">0</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>进阶优化：$6k \pm 1$ 法（面试推荐）</li></ol><p>这是一个非常聪明的优化。除了 $2$ 和 $3$ 之外，<strong>所有的质数都可以表示为 $6k-1$ 或 $6k+1$ 的形式。</strong></p><p><strong>为什么？</strong></p><p>我们将所有整数按 $6$ 的余数分类：</p><ul><li>$6k, 6k+2, 6k+3, 6k+4$ 分别能被 $6, 2, 3, 2$ 整除，显然不是质数（除非是 $2$ 和 $3$ 本身）。</li><li>剩下的只有 $6k+1$ 和 $6k+5$（即 $6k-1$）。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isPrime</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">3</span>) <span class="keyword">return</span> n &gt; <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 排除掉不在 6 附近波动的数</span></span><br><span class="line">    <span class="keyword">if</span> (n % <span class="number">2</span> == <span class="number">0</span> || n % <span class="number">3</span> == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 从 5 开始（即 6*1 - 1），步长为 6</span></span><br><span class="line">    <span class="comment">// 每次检查 i (6k-1) 和 i+2 (6k+1)</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">5</span>; i * i &lt;= n; i += <span class="number">6</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (n % i == <span class="number">0</span> || n % (i + <span class="number">2</span>) == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th><strong>方法</strong></th><th><strong>复杂度</strong></th><th><strong>优点</strong></th><th><strong>缺点</strong></th></tr></thead><tbody><tr><td><strong>暴力试除</strong></td><td>$O(n)$</td><td>简单到不需要脑子</td><td>$n &gt; 10^7$ 基本就废了</td></tr><tr><td><strong>平方根优化</strong></td><td>$O(\sqrt{n})$</td><td>逻辑清晰，最常用</td><td>处理 $10^{14}$ 以上的数开始吃力</td></tr><tr><td><strong>$6k \pm 1$ 优化</strong></td><td>$O(\frac{\sqrt{n}}{3})$</td><td>常数极小，比普通试除快 3 倍</td><td>需要记住 6 的倍数特性</td></tr><tr><td><strong>Miller-Rabin</strong></td><td>$O(k \log^3 n)$</td><td>解决天文数字级别的判定</td><td>实现复杂，需要大数处理</td></tr></tbody></table></div><h3 id="计算-1-到-n-之间的质数个数"><a href="#计算-1-到-n-之间的质数个数" class="headerlink" title="计算 $1$ 到 $n$ 之间的质数个数"></a>计算 $1$ 到 $n$ 之间的质数个数</h3><p>根据 $n$ 的范围不同，我们有三种主流的解决方法：</p><p><strong>埃氏筛法</strong></p><p>埃氏筛的核心思想非常朴素：<strong>质数的倍数一定不是质数。</strong></p><ul><li><strong>流程</strong>：<ol><li>创建一个长度为 $n+1$ 的布尔数组，初始全部设为“是质数”。</li><li>从 $2$ 开始往后扫描。</li><li>如果当前数 $i$ 是质数，就把它所有的倍数（$2i, 3i, \dots$）全部标记为“不是质数”。</li><li>为了优化，可以从 $i \times i$ 开始标记，因为更小的倍数（如 $2i$）在扫描到前面的数字时已经被标记过了。</li></ol></li><li><strong>复杂度</strong>：时间 $O(n \log \log n)$，空间 $O(n)$。</li><li><strong>评价</strong>：在 $n &lt; 10^7$ 时表现非常出色，代码实现极其简单。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sieve</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isPrime</span><span class="params">(n + <span class="number">1</span>, <span class="literal">true</span>)</span></span>;</span><br><span class="line">    isPrime[<span class="number">0</span>] = isPrime[<span class="number">1</span>] = <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isPrime[i]) &#123;</span><br><span class="line">            <span class="comment">// 从 i*i 开始标记，步长为 i</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i * i; j &lt;= n; j += i) &#123;</span><br><span class="line">                isPrime[j] = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; result;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isPrime[i]) result.<span class="built_in">push_back</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>线性筛法</strong></p><p>埃氏筛有一个小缺点：一个合数可能被多次重复标记（比如 $6$ 既会被 $2$ 筛掉，也会被 $3$ 筛掉）。线性筛通过<strong>让每个合数只被它的最小质因子筛掉</strong>，实现了严格的 $O(n)$ 复杂度。</p><blockquote><p>通过核心逻辑 <code>if (i % p == 0) break;</code>，保证<strong>每个合数只被它的最小质因子筛掉一次</strong>。</p></blockquote><ul><li><p><strong>核心逻辑</strong>：</p><p>维护一个质数列表。对于每一个数 $i$，遍历已找到的质数 $p$：</p><ol><li>标记 $i \times p$ 为合数。</li><li><strong>关键停止条件</strong>：如果 <code>i % p == 0</code>，立即停止。这保证了 $i \times (\text{下一个质数})$ 会被那个质数更小的因子筛掉，从而避免重复。</li></ol></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">countPrimes</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; primes;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isPrime</span><span class="params">(n, <span class="literal">true</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt; n; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isPrime[i]) primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i * p &gt;= n) <span class="keyword">break</span>;</span><br><span class="line">            isPrime[i * p] = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">if</span> (i % p == <span class="number">0</span>) <span class="keyword">break</span>; <span class="comment">// 核心：避免重复筛选</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> primes.<span class="built_in">size</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> <strong>分块筛或 Meissel-Lehmer 算法</strong></p><p>如果遇到的 $n$ 达到了 $10^{10}$ 甚至更高，内存开不下 $O(n)$ 的数组，线性筛就失效了。</p><p><strong>分块筛</strong>：将 $1$ 到 $n$ 分成若干个小块，每块大小约 $\sqrt{n}$，逐块统计，空间复杂度降至 $O(\sqrt{n})$。</p><p><strong>Meissel-Lehmer 算法</strong>：这是一种基于数论组合公式的方法，不需要遍历所有数，就能直接计算出质数个数，复杂度约为 $O(n^{2/3})$。</p><p>1175.请你帮忙给从 <code>1</code> 到 <code>n</code> 的数设计排列方案，使得所有的「质数」都应该被放在「质数索引」（索引从 1 开始）上；你需要返回可能的方案总数。</p><p>让我们一起来回顾一下「质数」：质数一定是大于 1 的，并且不能用两个小于它的正整数的乘积来表示。</p><p>由于答案可能会很大，所以请你返回答案 <strong>模 mod <code>10^9 + 7</code></strong> 之后的结果即可。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// long factorial(int n) &#123;</span></span><br><span class="line">    <span class="comment">//     long res = 1;</span></span><br><span class="line">    <span class="comment">//     for (int i = 1; i &lt;= n; i++) &#123;</span></span><br><span class="line">    <span class="comment">//         res *= i;</span></span><br><span class="line">    <span class="comment">//         res %= MOD;</span></span><br><span class="line">    <span class="comment">//     &#125;</span></span><br><span class="line">    <span class="comment">//     return res;</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">factorial</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(n==<span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> (n * <span class="built_in">factorial</span>(n - <span class="number">1</span>)) % (MOD);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> MOD = <span class="number">1e9</span> + <span class="number">7</span>;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">calcPrimesCount</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isPrimes</span><span class="params">(<span class="number">1</span> + n, <span class="literal">true</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> cnt = n - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isPrimes[i]) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> j = i * i; j &lt;= n; j += i) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (isPrimes[j]) &#123;</span><br><span class="line">                        isPrimes[j] = <span class="literal">false</span>;</span><br><span class="line">                        cnt--;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 线性筛</span></span><br><span class="line">     <span class="function"><span class="type">int</span> <span class="title">LinearcalcPrimesCount</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isPrimes</span><span class="params">(<span class="number">1</span> + n, <span class="literal">true</span>)</span></span>;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; primes;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isPrimes[i]) &#123;</span><br><span class="line">                primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span>&amp; t : primes) &#123;</span><br><span class="line">                <span class="type">int</span> num = t * i;</span><br><span class="line">                <span class="keyword">if</span> (num &gt; n) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                isPrimes[num] = <span class="literal">false</span>;</span><br><span class="line">                <span class="comment">// 排除</span></span><br><span class="line">                <span class="keyword">if</span> (i % t == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> primes.<span class="built_in">size</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">numPrimeArrangements</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 计算1-n的质数个数</span></span><br><span class="line">        <span class="comment">// 结果m!(n-m)!</span></span><br><span class="line">        <span class="type">int</span> m = <span class="built_in">calcPrimesCount</span>(n);</span><br><span class="line">        <span class="comment">// 2 3</span></span><br><span class="line">        <span class="keyword">return</span> (<span class="type">int</span>) (<span class="built_in">factorial</span>(m) * <span class="built_in">factorial</span>(n - m) % MOD);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>模运算有一个非常重要的性质：</p><script type="math/tex; mode=display">(a \times b) \pmod m = ((a \pmod m) \times (b \pmod m)) \pmod m</script><p>这意味着，如果你要计算一连串数字的乘积并取模，你可以<strong>在中间任何一步</strong>取模，结果都不会改变。</p><div class="table-container"><table><thead><tr><th><strong>运算类型</strong></th><th><strong>规则</strong></th><th><strong>是否等价</strong></th></tr></thead><tbody><tr><td><strong>加法</strong></td><td>$(a + b) \% m = (a\%m + b\%m) \% m$</td><td><strong>是</strong></td></tr><tr><td><strong>减法</strong></td><td>$(a - b) \% m = (a\%m - b\%m + m) \% m$</td><td><strong>是</strong>（需加 $m$ 防止负数）</td></tr><tr><td><strong>乘法</strong></td><td>$(a \times b) \% m = (a\%m \times b\%m) \% m$</td><td><strong>是</strong></td></tr><tr><td><strong>除法</strong></td><td>$(a / b) \% m$</td><td><strong>否！</strong>（需使用<strong>逆元</strong>）</td></tr></tbody></table></div><p>如果需要<strong>永远返回正数</strong>的取模结果（例如在处理循环数组下标时），可以使用这个通用的“数学模”技巧：</p><script type="math/tex; mode=display">\text{positive\_mod} = (a \% b + b) \% b</script><h3 id="一个数的因数与一个数的质因数"><a href="#一个数的因数与一个数的质因数" class="headerlink" title="一个数的因数与一个数的质因数"></a>一个数的因数与一个数的质因数</h3><p><strong>求解一个数的所有因数</strong></p><p>如果 $n \pmod i == 0$，那么 $i$ 就是 $n$ 的因数。 <strong>核心思路：</strong> 利用对称性。如果 $i$ 是 $n$ 的因数，那么 $n/i$ 必然也是 $n$ 的因数。因此，我们只需要遍历到 $\sqrt{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><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></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">getDivisors</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(i); <span class="comment">// 较小的因数</span></span><br><span class="line">            <span class="keyword">if</span> (i * i != n) &#123;</span><br><span class="line">                res.<span class="built_in">push_back</span>(n / i); <span class="comment">// 对称的较大因数（避免完全平方数重复计算）</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">sort</span>(res.<span class="built_in">begin</span>(), res.<span class="built_in">end</span>()); <span class="comment">// 如果需要有序</span></span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>求解一个数的质因数分解</strong></p><p>将一个合数表示成若干个质数相乘的形式。 <strong>核心思路：</strong> 试除法。从最小的质数 $2$ 开始尝试，只要能整除，就一直除下去，直到除不动为止，然后再试下一个数。</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></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">getPrimeFactors</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(i);</span><br><span class="line">            n /= i; <span class="comment">// 关键：除掉已经找到的质因子</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">1</span>) res.<span class="built_in">push_back</span>(n); <span class="comment">// 如果最后剩下的数大于1，说明它是最后一个质因子</span></span><br><span class="line">    <span class="keyword">return</span> res;</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><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></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">getDistinctPrimeFactors</span><span class="params">(<span class="type">long</span> <span class="type">long</span> n)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">long</span> <span class="type">long</span> i = <span class="number">2</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(i); <span class="comment">// 记录这个不同的质因数</span></span><br><span class="line">            <span class="keyword">while</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">                n /= i; <span class="comment">// 关键：彻底除尽，把所有的 i 都从 n 中剥离</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果最后 n &gt; 1，剩下的 n 本身就是一个质数</span></span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        res.<span class="built_in">push_back</span>(n);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2521给你一个正整数数组 <code>nums</code> ，对 <code>nums</code> 所有元素求积之后，找出并返回乘积中 <strong>不同质因数</strong> 的数目。</p><p><strong>注意：</strong></p><ul><li><strong>质数</strong> 是指大于 <code>1</code> 且仅能被 <code>1</code> 及自身整除的数字。</li><li>如果 <code>val2 / val1</code> 是一个整数，则整数 <code>val1</code> 是另一个整数 <code>val2</code> 的一个因数。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">distinctPrimeFactors</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 求解数组中每个数的不同质因数</span></span><br><span class="line">        unordered_set&lt;<span class="type">int</span>&gt; uset;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span> n : nums) &#123;</span><br><span class="line">            <span class="comment">// 计算每个n的不同质因数</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">                    uset.<span class="built_in">insert</span>(i);</span><br><span class="line">                    <span class="keyword">while</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">                        n /= i;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (n &gt; <span class="number">1</span>) &#123;</span><br><span class="line">                uset.<span class="built_in">insert</span>(n);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> uset.<span class="built_in">size</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="1-到-N-范围内所有数的质因数"><a href="#1-到-N-范围内所有数的质因数" class="headerlink" title="$1$ 到 $N$ 范围内所有数的质因数"></a>$1$ 到 $N$ 范围内<strong>所有数</strong>的质因数</h3><p>利用<strong>线性筛（欧拉筛）进行预处理，通过维护一个最小质因子数组 (Minimum Prime Factor, MPF)</strong>，将每个数的分解过程优化到极速。</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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> MAXN = <span class="number">1000005</span>;</span><br><span class="line"><span class="type">int</span> min_prime[MAXN]; <span class="comment">// 记录每个数的最小质因子</span></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; primes;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">sieve</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (min_prime[i] == <span class="number">0</span>) &#123; <span class="comment">// i 是质数</span></span><br><span class="line">            min_prime[i] = i;    <span class="comment">// 质数的最小质因子是它自己</span></span><br><span class="line">            primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">            <span class="keyword">if</span> (p &gt; min_prime[i] || i * p &gt; n) <span class="keyword">break</span>;</span><br><span class="line">            min_prime[i * p] = p; <span class="comment">// 记录合数的最小质因子</span></span><br><span class="line">            <span class="keyword">if</span> (i % p == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">factorize</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; factors;</span><br><span class="line">    <span class="keyword">while</span> (x &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        factors.<span class="built_in">push_back</span>(min_prime[x]); <span class="comment">// 拿到当前最小质因子</span></span><br><span class="line">        x /= min_prime[x];               <span class="comment">// 直接除掉它</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> factors;</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><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></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> MAXN = <span class="number">1e6</span> + <span class="number">5</span>;</span><br><span class="line"><span class="type">int</span> min_p[MAXN];    <span class="comment">// 存储最小质因子</span></span><br><span class="line"><span class="type">int</span> f[MAXN];        <span class="comment">// 存储质因子相关的递推属性（如质因子总数）</span></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; primes;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">linear_sieve</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    f[<span class="number">1</span>] = <span class="number">0</span>; <span class="comment">// 1 没有质因子</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (min_p[i] == <span class="number">0</span>) &#123; <span class="comment">// i 是质数</span></span><br><span class="line">            min_p[i] = i;</span><br><span class="line">            primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">            f[i] = <span class="number">1</span>; <span class="comment">// 质数本身只有 1 个质因子</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">            <span class="keyword">if</span> (p &gt; min_p[i] || i * p &gt; n) <span class="keyword">break</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="type">int</span> target = i * p;</span><br><span class="line">            min_p[target] = p; <span class="comment">// p 一定是 target 的最小质因子</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// --- 核心递推逻辑 ---</span></span><br><span class="line">            <span class="keyword">if</span> (i % p == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// p 已经是 i 的质因子</span></span><br><span class="line">                f[target] = f[i] + <span class="number">1</span>; <span class="comment">// 示例：总质因子数递增</span></span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// p 是一个全新的、更小的质因子</span></span><br><span class="line">                f[target] = f[i] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-到-N-范围内所有数的不同的质因数个数"><a href="#1-到-N-范围内所有数的不同的质因数个数" class="headerlink" title="$1$ 到 $N$ 范围内所有数的不同的质因数个数"></a>$1$ 到 $N$ 范围内<strong>所有数</strong>的不同的质因数个数</h3><p>如果只需要知道每个数有<strong>多少个不同</strong>的质因子，线性筛还可以在筛的过程中递推：</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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> distinct_count[MAXN];</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">count_sieve</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (min_prime[i] == <span class="number">0</span>) &#123; <span class="comment">// 质数</span></span><br><span class="line">            min_prime[i] = i;</span><br><span class="line">            distinct_count[i] = <span class="number">1</span>;</span><br><span class="line">            primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i * p &gt; n) <span class="keyword">break</span>;</span><br><span class="line">            min_prime[i * p] = p;</span><br><span class="line">            <span class="keyword">if</span> (i % p == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// p 是 i 的因子，i*p 和 i 的不同质因子种类一样</span></span><br><span class="line">                distinct_count[i * p] = distinct_count[i];</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// p 不是 i 的因子，i*p 比 i 多了一个质因子 p</span></span><br><span class="line">                distinct_count[i * p] = distinct_count[i] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-n中所有数的因数个数"><a href="#1-n中所有数的因数个数" class="headerlink" title="1-n中所有数的因数个数"></a>1-n中所有数的因数个数</h3><p>最容易理解和编写的方法。我们不关注某个数有哪些因数，而是关注<strong>每个数作为因数贡献了多少次</strong>。</p><p><strong>逻辑：</strong></p><ol><li>准备一个数组 <code>count[n+1]</code>，全部初始化为 0。</li><li>遍历 $i$ 从 $1$ 到 $n$（作为可能的因数）。</li><li>对于每个 $i$，找到它在 $n$ 范围内的所有倍数 $j = i, 2i, 3i, \dots$，并将 <code>count[j]</code> 加 1。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">countAllDivisors</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">count</span><span class="params">(n + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = i; j &lt;= n; j += i) &#123;</span><br><span class="line">            count[j]++; <span class="comment">// i 是 j 的一个因数</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果追求极致的 $O(n)$ 性能，可以利用<strong>约数个数定理</strong>和线性筛。</p><h4 id="约数个数定理"><a href="#约数个数定理" class="headerlink" title="约数个数定理"></a>约数个数定理</h4><p>如何利用质因数分解的结果，一秒求出某个数的所有因数个数</p><p><strong>约数个数定理：</strong></p><p>如果 $i$ 的质因数分解为 $p_1^{a_1} \cdot p_2^{a_2} \cdots p_k^{a_k}$，那么其因数个数为：</p><script type="math/tex; mode=display">d(i) = (a_1 + 1)(a_2 + 1) \cdots (a_k + 1)</script><p>假设一个数 $n$ 的质因数分解结果为：</p><script type="math/tex; mode=display">n = p_1^{a_1} \times p_2^{a_2} \times \dots \times p_k^{a_k}</script><p>（其中 $p$ 是质因数，$a$ 是该质因数的指数）</p><p>那么 $n$ 的所有正因数个数为：</p><script type="math/tex; mode=display">\text{因数个数} = (a_1 + 1) \times (a_2 + 1) \times \dots \times (a_k + 1)</script><p>如果你需要批量处理，可以利用之前提到的 <code>min_prime</code>（最小质因子）数组，配合递推公式在 $O(N)$ 时间内完成。</p><p><strong>算法逻辑：</strong></p><p>我们维护两个数组：</p><ol><li><code>d[i]</code>：数字 $i$ 的因数个数。</li><li><code>num[i]</code>：数字 $i$ 的最小质因子的幂次（即上面的 $a_1$）。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> MAXN = <span class="number">1000005</span>;</span><br><span class="line"><span class="type">int</span> d[MAXN], num[MAXN], primes[MAXN], cnt;</span><br><span class="line"><span class="type">bool</span> is_prime[MAXN];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">get_divisors</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    d[<span class="number">1</span>] = <span class="number">1</span>; <span class="comment">// 1 只有一个因数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!is_prime[i]) &#123;</span><br><span class="line">            primes[++cnt] = i;</span><br><span class="line">            d[i] = <span class="number">2</span>;   <span class="comment">// 质数只有 1 和自己，共 2 个因数</span></span><br><span class="line">            num[i] = <span class="number">1</span>; <span class="comment">// 最小质因子 i 的幂次是 1</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= cnt &amp;&amp; i * primes[j] &lt;= n; j++) &#123;</span><br><span class="line">            is_prime[i * primes[j]] = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">if</span> (i % primes[j] == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果 primes[j] 是 i 的最小质因子</span></span><br><span class="line">                num[i * primes[j]] = num[i] + <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 利用公式：原本是 (num[i]+1)，现在变成 (num[i]+2)</span></span><br><span class="line">                d[i * primes[j]] = d[i] / (num[i] + <span class="number">1</span>) * (num[i * primes[j]] + <span class="number">1</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 如果 primes[j] 是比 i 的最小质因子还要小的质数</span></span><br><span class="line">                num[i * primes[j]] = <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 新质因子的指数是 1，所以因数个数直接乘以 (1+1)=2</span></span><br><span class="line">                d[i * primes[j]] = d[i] * <span class="number">2</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// i: 当前处理的数, p: 当前遍历到的质数</span></span><br><span class="line"><span class="keyword">if</span> (i % p == <span class="number">0</span>) &#123; </span><br><span class="line">    <span class="comment">// 情况 B：p 已经是 i 的最小质子</span></span><br><span class="line">    e[i * p] = e[i] + <span class="number">1</span>; <span class="comment">// 指数加1</span></span><br><span class="line">    d[i * p] = d[i] / (e[i] + <span class="number">1</span>) * (e[i * p] + <span class="number">1</span>); <span class="comment">// 更新总个数</span></span><br><span class="line">    <span class="keyword">break</span>; <span class="comment">// 线性筛核心：找到最小质因子就停止</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 情况 A：p 是全新的最小质子</span></span><br><span class="line">    e[i * p] = <span class="number">1</span>;      <span class="comment">// 新质子的指数就是1</span></span><br><span class="line">    d[i * p] = d[i] * <span class="number">2</span>; <span class="comment">// 总个数直接翻倍 (因为 (1+1)=2 )</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="因数之和"><a href="#因数之和" class="headerlink" title="因数之和"></a>因数之和</h3><p>假设一个数 $n$ 的质因数分解结果为：</p><script type="math/tex; mode=display">n = p_1^{a_1} \times p_2^{a_2} \times \dots \times p_k^{a_k}</script><p>那么 $n$ 的所有正约数之和为：</p><script type="math/tex; mode=display">\sigma(n) = (1 + p_1 + p_1^2 + \dots + p_1^{a_1}) \times (1 + p_2 + p_2^2 + \dots + p_2^{a_2}) \times \dots \times (1 + p_k + p_k^{a_k})</script><p>要在 $O(N)$ 时间内递推，逻辑和之前求个数的方法（维护最小质因子指数）极其相似，但这次我们需要维护：</p><ul><li><code>sigma[i]</code>：$i$ 的约数之和。</li><li><code>g[i]</code>：$i$ 的<strong>最小质因子贡献的那一部分和</strong>（即公式中第一个括号的值：$1 + p_1 + \dots + p_1^{a_1}$）。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> MAXN = <span class="number">1000005</span>;</span><br><span class="line"><span class="type">long</span> <span class="type">long</span> sigma[MAXN], g[MAXN]; <span class="comment">// sigma 存总和，g 存最小质因子部分的等比数列和</span></span><br><span class="line"><span class="type">int</span> primes[MAXN], cnt;</span><br><span class="line"><span class="type">bool</span> not_prime[MAXN];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">get_sigma</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    sigma[<span class="number">1</span>] = <span class="number">1</span>; <span class="comment">// 1 的约数之和就是 1</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!not_prime[i]) &#123;</span><br><span class="line">            primes[++cnt] = i;</span><br><span class="line">            sigma[i] = i + <span class="number">1</span>; <span class="comment">// 质数 p 的约数之和是 1 + p</span></span><br><span class="line">            g[i] = i + <span class="number">1</span>;     <span class="comment">// 最小质因子部分也是 1 + p</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= cnt &amp;&amp; i * primes[j] &lt;= n; j++) &#123;</span><br><span class="line">            not_prime[i * primes[j]] = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">if</span> (i % primes[j] == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 情况 A：primes[j] 是 i 的最小质因子</span></span><br><span class="line">                <span class="comment">// 新的 g = 原有的 g 乘上 p 再加 1 (例如：从 1+2+4 变成 1+2+4+8)</span></span><br><span class="line">                g[i * primes[j]] = g[i] * primes[j] + <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 更新总和：先除掉旧的最小质因子部分，再乘上新的</span></span><br><span class="line">                sigma[i * primes[j]] = sigma[i] / g[i] * g[i * primes[j]];</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 情况 B：primes[j] 是一个新的、更小的质因子</span></span><br><span class="line">                g[i * primes[j]] = primes[j] + <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 直接乘上新质因子的贡献 (1 + p)</span></span><br><span class="line">                sigma[i * primes[j]] = sigma[i] * (primes[j] + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th><strong>属性</strong></th><th><strong>约数个数 d(n)</strong></th><th><strong>约数之和 σ(n)</strong></th></tr></thead><tbody><tr><td><strong>基础单位</strong></td><td>指数 $(a_i + 1)$</td><td>等比数列和 $(1 + p_i + \dots + p_i^{a_i})$</td></tr><tr><td><strong>质数 $p$ 的值</strong></td><td>$2$</td><td>$p + 1$</td></tr><tr><td><strong>递推核心</strong></td><td>维护最小质因子的<strong>指数</strong></td><td>维护最小质因子的<strong>等比数列和</strong></td></tr></tbody></table></div><h4 id="常见质因数属性的递推公式"><a href="#常见质因数属性的递推公式" class="headerlink" title="常见质因数属性的递推公式"></a>常见质因数属性的递推公式</h4><p>利用上面的模板，你可以在 $O(N)$ 内一次性求出以下所有属性：</p><p>A. 质因子总数（包含重复）</p><ul><li><strong>含义</strong>：$12 = 2 \times 2 \times 3 \to f(12) = 3$。</li><li><strong>递推</strong>：$f(i \cdot p) = f(i) + 1$。</li></ul><p>B. 不同质因子的个数</p><ul><li><strong>含义</strong>：$12 = 2^2 \times 3 \to f(12) = 2$。</li><li><strong>递推</strong>：<ul><li>如果 <code>i % p == 0</code>：$f(i \cdot p) = f(i)$（$p$ 已经出现过了）。</li><li>如果 <code>i % p != 0</code>：$f(i \cdot p) = f(i) + 1$（$p$ 是新面孔）。</li></ul></li></ul><p>C. 最小质因子的幂次（指数）</p><ul><li><strong>含义</strong>：$12 = 2^2 \times 3 \to f(12) = 2$。</li><li><strong>递推</strong>：<ul><li>如果 <code>i % p == 0</code>：$f(i \cdot p) = f(i) + 1$。</li><li>如果 <code>i % p != 0</code>：$f(i \cdot p) = 1$。</li></ul></li></ul><p><strong>四因数</strong></p><p>给你一个整数数组 <code>nums</code>，请你返回该数组中恰有四个因数的这些整数的各因数之和。如果数组中不存在满足题意的整数，则返回 <code>0</code> </p><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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">sumFourDivisors</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> totalSum = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> n : nums) &#123;</span><br><span class="line">            <span class="type">int</span> count = <span class="number">0</span>;</span><br><span class="line">            <span class="type">int</span> currentSum = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i * i &lt;= n; ++i) &#123;</span><br><span class="line">                <span class="keyword">if</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">                    count++;</span><br><span class="line">                    currentSum += i;</span><br><span class="line">                    <span class="keyword">if</span> (i * i != n) &#123; <span class="comment">// 避免平方数重复计算</span></span><br><span class="line">                        count++;</span><br><span class="line">                        currentSum += n / i;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (count &gt; <span class="number">4</span>) <span class="keyword">break</span>; <span class="comment">// 剪枝优化</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (count == <span class="number">4</span>) &#123;</span><br><span class="line">                totalSum += currentSum;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> totalSum;</span><br><span class="line">    &#125;</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">sumFourDivisors</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> N = <span class="number">100000</span>;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; primes;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isNotPrime</span><span class="params">(N + <span class="number">1</span>, <span class="literal">false</span>)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">d</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>;          <span class="comment">// 因数个数</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">long</span> <span class="type">long</span>&gt; <span class="title">sigma</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>; <span class="comment">// 因数之和</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">a</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>;          <span class="comment">// 最小质因子的指数</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">long</span> <span class="type">long</span>&gt; <span class="title">g</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>;    <span class="comment">// 最小质因子的等比数列和</span></span><br><span class="line"></span><br><span class="line">        d[<span class="number">1</span>] = <span class="number">1</span>; sigma[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= N; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!isNotPrime[i]) &#123;</span><br><span class="line">                primes.<span class="built_in">push_back</span>(i);</span><br><span class="line">                d[i] = <span class="number">2</span>;</span><br><span class="line">                sigma[i] = i + <span class="number">1</span>;</span><br><span class="line">                a[i] = <span class="number">1</span>;</span><br><span class="line">                g[i] = i + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i * p &gt; N) <span class="keyword">break</span>;</span><br><span class="line">                isNotPrime[i * p] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">if</span> (i % p == <span class="number">0</span>) &#123;</span><br><span class="line">                    a[i * p] = a[i] + <span class="number">1</span>;</span><br><span class="line">                    d[i * p] = d[i] / (a[i] + <span class="number">1</span>) * (a[i * p] + <span class="number">1</span>);</span><br><span class="line">                    g[i * p] = g[i] * p + <span class="number">1</span>;</span><br><span class="line">                    sigma[i * p] = sigma[i] / g[i] * g[i * p];</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    a[i * p] = <span class="number">1</span>;</span><br><span class="line">                    d[i * p] = d[i] * <span class="number">2</span>;</span><br><span class="line">                    g[i * p] = p + <span class="number">1</span>;</span><br><span class="line">                    sigma[i * p] = sigma[i] * (p + <span class="number">1</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> ans = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> n : nums) &#123;</span><br><span class="line">            <span class="keyword">if</span> (d[n] == <span class="number">4</span>) ans += sigma[n];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="进制转换"><a href="#进制转换" class="headerlink" title="进制转换"></a>进制转换</h2><h3 id="十进制转七进制"><a href="#十进制转七进制" class="headerlink" title="十进制转七进制"></a>十进制转七进制</h3><p>给定一个整数 <code>num</code>，将其转化为 <strong>7 进制</strong>，并以字符串形式输出。</p><p><code>进制转换</code>类型的题，通常是利用除法和取模（mod）来进行计算，同时也要注意一些细节，如负数和零。如果输出是数字类型而非字符串，则也需要考虑是否会超出整数上下界。</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></pre></td><td class="code"><pre><span class="line"><span class="function">string <span class="title">convertToBase7</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (num == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;0&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    string base7;</span><br><span class="line">    <span class="type">bool</span> is_negative = num &lt; <span class="number">0</span>;</span><br><span class="line">    num = <span class="built_in">abs</span>(num);</span><br><span class="line">    <span class="keyword">while</span> (num) &#123;</span><br><span class="line">        <span class="type">int</span> quotient = num / <span class="number">7</span>, remainder = num % <span class="number">7</span>;</span><br><span class="line">        base7 = <span class="built_in">to_string</span>(remainder) + base7;</span><br><span class="line">        num = quotient;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> is_negative ? <span class="string">&quot;-&quot;</span> + base7 : base7;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>给定一个非负整数，判断它的阶乘结果的结尾有几个 0。</p><p>每个尾部的 0 由 2 × 5 =10 而来，因此我们可以把阶乘的每一个元素拆成质数相乘，统计有多少个 2 和 5。明显的，质因子 2 的数量远多于质因子 5 的数量，因此我们可以只统计阶乘结果里有多少个质因子 5。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">trailingZeroes</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 尾随0的个数就是质因数5的个数</span></span><br><span class="line">        <span class="comment">// 计算5-n中质因数为5的个数</span></span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">5</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="type">int</span> tmp = i;</span><br><span class="line">            <span class="keyword">while</span> (tmp % <span class="number">5</span> == <span class="number">0</span>) &#123;</span><br><span class="line">                cnt++;</span><br><span class="line">                tmp /= <span class="number">5</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>字符串相加。给定两个字符串形式的非负整数 <code>num1</code> 和<code>num2</code> ，计算它们的和并同样以字符串形式返回。</p><p>你不能使用任何內建的用于处理大整数的库（比如 <code>BigInteger</code>）， 也不能直接将输入的字符串转换为整数形式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">addStrings</span><span class="params">(string num1, string num2)</span> </span>&#123;</span><br><span class="line">        string res;</span><br><span class="line">        <span class="type">int</span> sz1 = num<span class="number">1.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> sz2 = num<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> i = sz1 - <span class="number">1</span>, j = sz2 - <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> car&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (i &gt;= <span class="number">0</span> || j &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">int</span> r = car;</span><br><span class="line">            r += (i &gt;= <span class="number">0</span>) ? (num1[i--] - <span class="string">&#x27;0&#x27;</span>) : <span class="number">0</span>;</span><br><span class="line">            r += (j &gt;= <span class="number">0</span>) ? (num2[j--] - <span class="string">&#x27;0&#x27;</span>) : <span class="number">0</span>;</span><br><span class="line">            res = <span class="built_in">to_string</span>(r % <span class="number">10</span>) + res;</span><br><span class="line">            car = r / <span class="number">10</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (car &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            res = <span class="string">&quot;1&quot;</span> + res;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>实现 <a href="https://www.cplusplus.com/reference/valarray/pow/">pow(<em>x</em>, <em>n</em>)</a> ，即计算 <code>x</code> 的整数 <code>n</code> 次幂函数（即，<code>xn</code> ）。</p><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">double</span> <span class="title">myPow</span><span class="params">(<span class="type">double</span> x, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (n == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (x == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n == numeric_limits&lt;<span class="type">int</span>&gt;::<span class="built_in">min</span>()) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span> / (x * <span class="built_in">myPow</span>(x, numeric_limits&lt;<span class="type">int</span>&gt;::<span class="built_in">max</span>()));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span> / <span class="built_in">myPow</span>(x, -n);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n % <span class="number">2</span> != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> x * <span class="built_in">myPow</span>(x, n - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">double</span> myPowSqrt = <span class="built_in">myPow</span>(x, n &gt;&gt; <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> myPowSqrt * myPowSqrt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="随机取样"><a href="#随机取样" class="headerlink" title="随机取样"></a>随机取样</h2><p>给定一个数组，要求实现两个指令函数。第一个函数“shuﬄe”可以随机打乱这个数组，第二个函数“reset”可以恢复原来的顺序。</p><p>采用经典的 <code>Fisher-Yates 洗牌算法</code>，原理是通过随机交换位置来实现随机打乱，有正向和反向两种写法，且实现非常方便。注意这里“reset”函数以及 Solution 类的构造函数的实现细节。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; nums;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; onums;</span><br><span class="line">    <span class="built_in">Solution</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) :<span class="built_in">nums</span>(nums), <span class="built_in">onums</span>(nums) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">reset</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> onums; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">shuffle</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 反向</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = nums.<span class="built_in">size</span>() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="built_in">swap</span>(nums[i], nums[<span class="built_in">rand</span>() % (i + <span class="number">1</span>)]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nums;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>洗牌算法</strong>：</p><p>对于数组<code>nums</code>，其长度为<code>n</code>。我们调用<code>shuffle()</code>方法返回的数组，应该有<em>n</em>!种可能，我们就可以说<code>shuffle()</code>方法返回的数组是随机的。</p><p>那最简单直白的做法就是，将数组<code>nums</code>的<em>n</em>!种排列组合都提前生成出来，每调用一次<code>shuffle()</code>，就取一种<code>nums</code>的排列组合出来返回。</p><p>这时候就轮到我们的<strong>洗牌算法</strong>出场了。不需要提前把<em>n</em>!种排列组合都生成好。</p><p>洗牌算法的思路很简单。我们有个长度为<em>n</em>的数组<code>nums</code>，对于每个<code>nums[i]</code>来说，都生成一个[<em>i</em>,<em>n</em>−1]范围的随机数，作为<code>random_idx</code>，然后交换<code>nums[i]</code>和<code>nums[random_idx</code>。</p><p>为什么说<strong>洗牌算法</strong>实现的<code>shuffle()</code>返回的数组会有<em>n</em>!种可能呢？</p><ul><li>对于<code>nums[0]</code>，它可能会和[0,<em>n</em>−1]范围内的任何一个数交换，有<em>n</em>种可能。</li><li>对于<code>nums[1]</code>，它可能会和[1,<em>n</em>−1]范围内的任何一个数交换，有<em>n</em>−1种可能。</li><li>…</li><li>对于<code>nums[n-1]</code>，它只能和<code>nums[n-1]</code>自己交换，只有1种可能。</li></ul><p>所以总的可能性是: <em>n</em>+(<em>n</em>−1)+(<em>n</em>−2)+…+1=<em>n</em>!</p><p>按权重随机选择</p><p>给你一个 <strong>下标从 0 开始</strong> 的正整数数组 <code>w</code> ，其中 <code>w[i]</code> 代表第 <code>i</code> 个下标的权重。</p><p>请你实现一个函数 <code>pickIndex</code> ，它可以 <strong>随机地</strong> 从范围 <code>[0, w.length - 1]</code> 内（含 <code>0</code> 和 <code>w.length - 1</code>）选出并返回一个下标。选取下标 <code>i</code> 的 <strong>概率</strong> 为 <code>w[i] / sum(w)</code> 。</p><ul><li><p>例如，对于 <code>w = [1, 3]</code>，挑选下标 <code>0</code> 的概率为 <code>1 / (1 + 3) = 0.25</code> （即，25%），而选取下标 <code>1</code> 的概率为 <code>3 / (1 + 3) = 0.75</code>（即，<code>75%</code>）。</p><p>前缀和+二分</p></li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt;&amp; w;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; prefix_sum;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Solution</span>(vector&lt;<span class="type">int</span>&gt;&amp; w) : <span class="built_in">w</span>(w), <span class="built_in">prefix_sum</span>(w.<span class="built_in">size</span>()) &#123;</span><br><span class="line">        <span class="comment">// 2 3 5</span></span><br><span class="line">        prefix_sum[<span class="number">0</span>] = w[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; prefix_sum.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            prefix_sum[i] = prefix_sum[i - <span class="number">1</span>] + w[i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lower_bound</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 求第一个大于等于val的索引</span></span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>, right = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &gt;= val) &#123;</span><br><span class="line">                res = mid;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">pickIndex</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 计算前缀和数组</span></span><br><span class="line">        <span class="type">int</span> randomVal = (<span class="built_in">rand</span>() % (prefix_sum.<span class="built_in">back</span>())) + <span class="number">1</span>;</span><br><span class="line">        <span class="comment">// 判断生成的randomIdx在哪个区间 lower_bound</span></span><br><span class="line">        <span class="type">int</span> res_idx = <span class="built_in">lower_bound</span>(prefix_sum, randomVal);</span><br><span class="line">        <span class="keyword">return</span> res_idx;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Your Solution object will be instantiated and called as such:</span></span><br><span class="line"><span class="comment"> * Solution* obj = new Solution(w);</span></span><br><span class="line"><span class="comment"> * int param_1 = obj-&gt;pickIndex();</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2026/01/27/ZOiU1tYajTzcCMf.png" alt="image-20260127200828547"></p><p><a href="https://leetcode.cn/problems/add-without-plus-lcci/">不用加号的加法</a></p><p>设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。</p><p>考虑两个二进制位相加的四种情况如下：</p><p>0 + 0 = 0<br>0 + 1 = 1<br>1 + 0 = 1<br>1 + 1 = 0 (进位)<br>可以发现，对于整数 a 和 b：</p><p>在不考虑进位的情况下，其无进位加法结果为 a⊕b。</p><p>而所有需要进位的位为 a &amp; b，进位后的进位结果为 (a &amp; b) &lt;&lt; 1。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">add</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 0 0 0</span></span><br><span class="line">        <span class="comment">// 0 1 0</span></span><br><span class="line">        <span class="comment">// 1 1 1</span></span><br><span class="line">        <span class="comment">// 不考虑进位 ,加法结果  s = a^b;</span></span><br><span class="line">        <span class="comment">// 进位如何计算 c = (a&amp;b)&lt;&lt;1;</span></span><br><span class="line">        <span class="keyword">while</span> (b != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">unsigned</span> <span class="type">int</span> car = (a &amp; b) &lt;&lt; <span class="number">1</span>;</span><br><span class="line">            a = a ^ b;</span><br><span class="line">            b = car;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> a;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>两数相除</p><p>给你两个整数，被除数 <code>dividend</code> 和除数 <code>divisor</code>。将两数相除，要求 <strong>不使用</strong> 乘法、除法和取余运算。</p><p>整数除法应该向零截断，也就是截去（<code>truncate</code>）其小数部分。例如，<code>8.345</code> 将被截断为 <code>8</code> ，<code>-2.7335</code> 将被截断至 <code>-2</code> 。</p><p>返回被除数 <code>dividend</code> 除以除数 <code>divisor</code> 得到的 <strong>商</strong> 。</p><p><strong>注意：</strong>假设我们的环境只能存储 <strong>32 位</strong> 有符号整数，其数值范围是 <code>[−231, 231 − 1]</code> 。本题中，如果商 <strong>严格大于</strong> <code>231 − 1</code> ，则返回 <code>231 − 1</code> ；如果商 <strong>严格小于</strong> <code>-231</code> ，则返回 <code>-231</code> 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">divide</span><span class="params">(<span class="type">int</span> dividend, <span class="type">int</span> divisor)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 处理最特殊的溢出情况</span></span><br><span class="line">        <span class="keyword">if</span> (dividend == INT_MIN &amp;&amp; divisor == <span class="number">-1</span>)</span><br><span class="line">            <span class="keyword">return</span> INT_MAX;</span><br><span class="line">        <span class="keyword">if</span> (dividend == INT_MIN &amp;&amp; divisor == <span class="number">1</span>)</span><br><span class="line">            <span class="keyword">return</span> INT_MIN;</span><br><span class="line">        <span class="comment">// 2. 确定最终符号</span></span><br><span class="line">        <span class="type">bool</span> negative = (dividend &gt; <span class="number">0</span>) ^ (divisor &gt; <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 全部转为负数处理，防止绝对值溢出</span></span><br><span class="line">        <span class="type">int</span> a = dividend &gt; <span class="number">0</span> ? -dividend : dividend;</span><br><span class="line">        <span class="type">int</span> b = divisor &gt; <span class="number">0</span> ? -divisor : divisor;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> res = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 4. 核心逻辑：利用位移寻找最大的倍数</span></span><br><span class="line">        <span class="keyword">while</span> (a &lt;= b) &#123;</span><br><span class="line">            <span class="type">int</span> value = b;</span><br><span class="line">            <span class="type">int</span> k = <span class="number">1</span>;</span><br><span class="line">            <span class="comment">// 这里的判断是为了防止 value &lt;&lt; 1 溢出</span></span><br><span class="line">            <span class="comment">// 注意 a 和 value 都是负数，所以是 &gt;=</span></span><br><span class="line">            <span class="keyword">while</span> (value &gt;= (INT_MIN &gt;&gt; <span class="number">1</span>) &amp;&amp; a &lt;= (value &lt;&lt; <span class="number">1</span>)) &#123;</span><br><span class="line">                value &lt;&lt;= <span class="number">1</span>;</span><br><span class="line">                k &lt;&lt;= <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            a -= value;</span><br><span class="line">            res += k;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> negative ? -res : res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">while (<span class="name">value</span> &gt;= (<span class="name">INT_MIN</span> &gt;&gt; <span class="number">1</span>) <span class="symbol">&amp;&amp;</span> a &lt;= (<span class="name">value</span> &lt;&lt; <span class="number">1</span>))</span><br></pre></td></tr></table></figure></blockquote><p>这行代码有两个判断条件：</p><ol><li><strong><code>value &gt;= (INT_MIN &gt;&gt; 1)</code></strong>：<ul><li>这是<strong>安全检查</strong>。</li><li>因为接下来我们要执行 <code>value &lt;&lt; 1</code>（即乘以 $2$）。如果 <code>value</code> 已经比 <code>INT_MIN</code> 的一半还要小了，再乘 $2$ 就会溢出。</li></ul></li><li><strong><code>a &lt;= (value &lt;&lt; 1)</code></strong>：<ul><li>这是<strong>空间检查</strong>。</li><li>翻译成白话：<strong>“如果我把现在除数再翻一倍，被除数 $a$ 还够不够减？”</strong></li><li>如果够减，就执行 <code>value &lt;&lt;= 1</code>（价值翻倍）和 <code>k &lt;&lt;= 1</code>（数量翻倍）。</li></ul></li></ol><p>字符串相乘</p><p>给定两个以字符串形式表示的非负整数 <code>num1</code> 和 <code>num2</code>，返回 <code>num1</code> 和 <code>num2</code> 的乘积，它们的乘积也表示为字符串形式。</p><p><strong>注意：</strong>不能使用任何内置的 BigInteger 库或直接将输入转换为整数。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">multiply</span><span class="params">(string num1, string num2)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//</span></span><br><span class="line">        <span class="keyword">if</span> (num1 == <span class="string">&quot;0&quot;</span> || num2 == <span class="string">&quot;0&quot;</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;0&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 3 4</span></span><br><span class="line">        <span class="type">int</span> n1 = num<span class="number">1.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n2 = num<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 12</span></span><br><span class="line">        <span class="comment">// 19</span></span><br><span class="line">        <span class="comment">//   0018</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">ans</span><span class="params">(n1 + n2)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = n1 - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = n2 - <span class="number">1</span>; j &gt;= <span class="number">0</span>; j--) &#123;</span><br><span class="line">                <span class="comment">// 相乘+低位</span></span><br><span class="line">                <span class="type">int</span> mul = (num1[i] - <span class="string">&#x27;0&#x27;</span>) * (num2[j] - <span class="string">&#x27;0&#x27;</span>) + ans[i + j + <span class="number">1</span>];</span><br><span class="line">                ans[i + j + <span class="number">1</span>] = mul % <span class="number">10</span>;</span><br><span class="line">                ans[i + j] += mul / <span class="number">10</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        string res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; ans.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (res.<span class="built_in">empty</span>() &amp;&amp; ans[i] == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            res += <span class="built_in">to_string</span>(ans[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> sum = mul + res[i + j + <span class="number">1</span>]; <span class="comment">// 1. 把当前的乘积加上这一位原有的数（包含之前的进位）</span></span><br><span class="line">res[i + j + <span class="number">1</span>] = sum % <span class="number">10</span>;      <span class="comment">// 2. 确定这一位的最终数字（0-9）</span></span><br><span class="line">res[i + j] += sum / <span class="number">10</span>;         <span class="comment">// 3. 把多出来的进位“送”给左边一位</span></span><br></pre></td></tr></table></figure><p>这种写法的精妙之处在于：<strong>它把复杂的进位处理变成了“原地滚雪球”。</strong> 你不需要写 <code>while</code> 循环去处理连续进位（比如 $999 + 1$），因为外层的 <code>i, j</code> 循环在向左移动时，会自动处理掉之前留在 <code>res[i+j]</code> 里的进位。</p><p>x的平方根</p><p>给你一个非负整数 <code>x</code> ，计算并返回 <code>x</code> 的 <strong>算术平方根</strong> 。</p><p>由于返回类型是整数，结果只保留 <strong>整数部分</strong> ，小数部分将被 <strong>舍去 。</strong></p><p><strong>注意：</strong>不允许使用任何内置指数函数和算符，例如 <code>pow(x, 0.5)</code> 或者 <code>x ** 0.5</code> </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">mySqrt</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 二分法</span></span><br><span class="line">        <span class="type">int</span> left = <span class="number">1</span>, right = x;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (mid &lt;= x/mid) &#123;</span><br><span class="line">                ans = mid;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>此外也可以用牛顿迭代法</p><p>计数质数</p><p>给定整数 <code>n</code> ，返回 <em>所有小于非负整数 <code>n</code> 的质数的数量</em> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">countPrimes</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> cnt = n - <span class="number">2</span>;</span><br><span class="line">        <span class="comment">// 埃氏筛</span></span><br><span class="line">        <span class="comment">// 质数的倍数不是质数</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isPrime</span><span class="params">(n, <span class="literal">true</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isPrime[i]) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">long</span> <span class="type">long</span> j = (<span class="type">long</span> <span class="type">long</span>)i * i; j &lt; n; j += i) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (isPrime[j]) &#123;</span><br><span class="line">                        cnt--;</span><br><span class="line">                        isPrime[j] = <span class="literal">false</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>丑数</p><p><strong>丑数</strong> 就是只包含质因数 <code>2</code>、<code>3</code> 和 <code>5</code> 的 <em>正</em> 整数。</p><p>给你一个整数 <code>n</code> ，请你判断 <code>n</code> 是否为 <strong>丑数</strong> 。如果是，返回 <code>true</code> ；否则，返回 <code>false</code> </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isUgly</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (n &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 计算一个数的所有质因数</span></span><br><span class="line">        <span class="comment">// 是否包含非2,3,5的正整数</span></span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; factor = &#123;<span class="number">2</span>,<span class="number">3</span>,<span class="number">5</span>&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; f:factor) &#123;</span><br><span class="line">            <span class="keyword">while</span>(n%f == <span class="number">0</span>) &#123;</span><br><span class="line">                n/=f;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> n==<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>质数:质数概念，质数筛选法</li><li>实现加减乘除、平方、次方以及开根号。</li><li>矩阵运算，矩阵基本性质，矩阵旋转。</li><li>最大公约数。</li><li>排列组合。</li></ul><h3 id="位运算"><a href="#位运算" class="headerlink" title="位运算"></a>位运算</h3><p><code>位运算</code>是算法题里比较特殊的一种类型，它们利用二进制位运算的特性进行一些奇妙的优化和计算。常用的位运算符号包括：</p><ul><li><code>∧</code>：按位异或</li><li><code>&amp;</code>：按位与</li><li><code>|</code>：按位或</li><li><code>~</code>：取反</li><li><code>&lt;&lt;</code>：算术左移</li><li><code>&gt;&gt;</code>：算术右移</li></ul><p>以下是一些常见的位运算特性，其中 <code>0s</code> 和 <code>1s</code> 分别表示只由 <code>0</code> 或 <code>1</code> 构成的二进制数字。</p><figure class="highlight text"><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">x ^ 0s = x      x &amp; 0s = 0     x | 0s = x</span><br><span class="line">x ^ 1s = ~x     x &amp; 1s = x     x | 1s = 1s</span><br><span class="line">x ^ x = 0       x &amp; x = x      x | x = x</span><br></pre></td></tr></table></figure><p>除此之外，n &amp; (n - 1) 可以去除 n 的位级表示中最低的那一位，例如对于二进制表示 11110100，减去 1 得到 11110011，这两个数按位与得到 11110000。<strong>n &amp; (-n) 可以得到 n 的位级表示中最低的那一位</strong>，例如对于二进制表示 11110100，取负得到 00001100，这两个数按位与得到 00000100。x - x&amp;(-x) = x&amp;(x-1)</p><h3 id="二进制特性"><a href="#二进制特性" class="headerlink" title="二进制特性"></a>二进制特性</h3><p>给定多个字母串，求其中任意两个字母串的长度乘积的最大值，且这两个字母串不能含有相同字母。</p><p>怎样快速判断两个字母串是否含有重复数字呢？可以为每个字母串建立一个长度为 26 的二进制数字，每个位置表示是否存在该字母。如果两个字母串含有重复数字，那它们的二进制表示的按位与不为 0。同时，我们可以建立一个哈希表来存储二进制数字到最长子母串长度的映射关系</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">maxProduct</span><span class="params">(vector&lt;string&gt;&amp; words)</span> </span>&#123;</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt; cache;</span><br><span class="line">    <span class="type">int</span> max_prod = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">const</span> string&amp; word : words) &#123;</span><br><span class="line">        <span class="type">int</span> mask = <span class="number">0</span>, w_len = word.<span class="built_in">length</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> c : word) &#123;</span><br><span class="line">            mask |= <span class="number">1</span> &lt;&lt; (c - ’a’);</span><br><span class="line">        &#125;</span><br><span class="line">        cache[mask] = <span class="built_in">max</span>(cache[mask], w_len);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span> [h_mask, h_len] : cache) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((mask &amp; h_mask) == <span class="number">0</span>) &#123;</span><br><span class="line">                max_prod = <span class="built_in">max</span>(max_prod, w_len * h_len);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> max_prod;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>给定一个非负整数 n，求从 0 到 n 的所有数字的二进制表达中，分别有多少个 1。</p><p>可以利用动态规划和位运算进行快速的求解。定义一个数组 dp，其中 dp[i] 表示数字 i 的二进制含有 1 的个数。对于第 i 个数字，如果它二进制的最后一位为 1，那么它含有 1 的个数则为 dp[i-1] + 1；如果它二进制的最后一位为 0，那么它含有 1 的个数和其算术右移结果相同，即 dp[i&gt;&gt;1]。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">countBits</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 一次遍历 状态递推</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">res</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="comment">// 如果i是奇数 res[i] = res[i-1]+1</span></span><br><span class="line">        <span class="comment">// 偶数 res[i] = res[i/2];</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &amp; <span class="number">1</span>) &#123;</span><br><span class="line">                res[i] = res[i - <span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                res[i] = res[i / <span class="number">2</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="回文"><a href="#回文" class="headerlink" title="回文"></a>回文</h1><h3 id="验证回文串"><a href="#验证回文串" class="headerlink" title="验证回文串"></a>验证回文串</h3><p>给你一个字符串 <code>s</code>，<strong>最多</strong> 可以从中删除一个字符。</p><p>请你判断 <code>s</code> 是否能成为回文字符串：如果能，返回 <code>true</code> ；否则，返回 <code>false</code> </p><p>考虑“最多删除一个字符，然后判断其能否成为回文字符串”。对上述回文字符串算法稍加改造，然后加上一些额外的逻辑来解决本题。我们仍然采用头/尾双指针的方法，并且更新指针的逻辑和上面也是一样的，不同之处如下。1．如果头/尾指针对应的字符相同，那么没有必要删除任何字符。2．如果头/尾指针对应的字符不同，那么必须删除一个字符才可能使之回文，并且由于只能删除一次，接下来只需要判断剩下的字符串是否能够构成回文即可。具体算法如下。</p><p>1．建立头/尾双指针l和r，分别指向字符串的第一个元素和最后一个元素。</p><p>2．如果l和r没有交会，则比较两个指针对应的字符。●　如果两个字符相同，则更新双指针，即l+=1，r-=1，重复执行步骤。●　如果两个字符不同，考虑删除左指针对应的字符或删除右指针对应的字符，并观察删除之后是否可以构成回文字符串。如果可以，则直接返回True；如果不可以，则直接返回False。</p><p>3．表示该字符串不需要删除字符就已经是回文字符串，直接返回True。</p><p>判断回文链表</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isPalindrome</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (x &lt; <span class="number">0</span> || (x != <span class="number">0</span> &amp;&amp; x % <span class="number">10</span> == <span class="number">0</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> reverse_num&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (x &gt; reverse_num) &#123;</span><br><span class="line">            reverse_num = reverse_num * <span class="number">10</span> + x % <span class="number">10</span>;</span><br><span class="line">            x /= <span class="number">10</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 字长度为奇数</span></span><br><span class="line">        <span class="keyword">return</span> reverse_num == x || x == reverse_num / <span class="number">10</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>最长回文串</p><p>给你一个字符串 <code>s</code>，找到 <code>s</code> 中最长的 回文 子串。</p><p> 动态规划或中心扩展</p><p>最长回文子序列</p><p>动态规划</p><p>用 <em>dp</em>[<em>i</em>][<em>j</em>] 表示字符串 <em>s</em> 的下标范围 [<em>i</em>,<em>j</em>] 内的最长回文子序列的长度</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">longestPalindromeSubseq</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划</span></span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(</span><br><span class="line">            sz,</span><br><span class="line">            <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(sz)); <span class="comment">// 以s[i-j]的子串中最长回文子序列</span></span><br><span class="line">        <span class="comment">// dp[i][j] = dp[i+1][j-1]+2, s[i] == s[j]</span></span><br><span class="line">        <span class="comment">// max(dp[i+1][j],dp[i][j-1])</span></span><br><span class="line">      <span class="comment">//  由于状态转移方程都是从长度较短的子序列向长度较长的子序列转移，因此需要注意动态规划的循环顺序</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = sz - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i; j &lt; sz; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i == j) &#123;</span><br><span class="line">                    dp[i][j] = <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (s[i] == s[j]) &#123;</span><br><span class="line">                        dp[i][j] = dp[i + <span class="number">1</span>][j - <span class="number">1</span>] + <span class="number">2</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        dp[i][j] = <span class="built_in">max</span>(dp[i + <span class="number">1</span>][j], dp[i][j - <span class="number">1</span>]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">0</span>][sz - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>滚动数组优化,由于dp[i][j]仅依赖于dp[i+1][j]和dp[i][j-1],使用滚动数组优化</p><blockquote><p>核心逻辑是：<strong>既然计算当前状态只依赖于前一个（或前几个）状态，那我们就没必要把整张 DP 大表都存在内存里。</strong></p></blockquote><p>在标准的 DP 中，我们通常会开一个很大的数组（比如 <code>dp[n][m]</code>）来记录每一个子问题的解。但很多时候，你在计算第 $i$ 行时，只会用到第 $i-1$ 行的数据，而第 $i-2, i-3$ 行的数据就变成了“过时信息”</p><p>两种常见的演进方式</p><p>第一种：模运算切换（双行滚动）</p><p>这种方式最直观。如果你发现 $dp[i]$ 只依赖于 $dp[i-1]$，你可以只开两个数组：<code>dp[0]</code> 和 <code>dp[1]</code>。</p><ul><li><strong>第 0 次</strong>：计算结果存入 <code>dp[0]</code></li><li><strong>第 1 次</strong>：根据 <code>dp[0]</code> 计算结果，存入 <code>dp[1]</code></li><li><strong>第 2 次</strong>：根据 <code>dp[1]</code> 计算结果，存入 <code>dp[0]</code>（覆盖掉没用的旧数据）</li></ul><p><strong>代码技巧：</strong></p><p>使用 <code>i % 2</code> 或者 <code>i &amp; 1</code> 来切换下标。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 优化前：dp[i] = dp[i-1] + dp[i-2]</span></span><br><span class="line"><span class="comment">// 优化后：</span></span><br><span class="line">dp[i % <span class="number">2</span>] = dp[(i - <span class="number">1</span>) % <span class="number">2</span>] + dp[(i - <span class="number">2</span>) % <span class="number">2</span>];</span><br></pre></td></tr></table></figure><p>第二种：单行覆盖（最极致的优化）</p><p>如果你能巧妙地安排计算顺序，甚至连两行都不需要，<strong>只需要一个一维数组</strong>。</p><p>最经典的例子是 <strong>0-1 背包问题</strong>：</p><ul><li><strong>原本</strong>：<code>dp[i][j]</code> 表示前 $i$ 个物品在容量为 $j$ 时的最大价值。</li><li><strong>优化后</strong>：<code>dp[j]</code>。</li><li><strong>关键点</strong>：为了防止在计算当前行时使用了“已经被更新过的当前行数据”（即重复放入物品），我们需要<strong>倒序遍历</strong>容量 $j$。</li></ul><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><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">longestPalindromeSubseq</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划</span></span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(sz)</span></span>; <span class="comment">// 以s[i-j]的子串中最长回文子序列</span></span><br><span class="line">        <span class="comment">// dp[i][j] = dp[i+1][j-1]+2, s[i] == s[j]</span></span><br><span class="line">        <span class="comment">// max(dp[i+1][j],dp[i][j-1])</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">tmp</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = sz - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i; j &lt; sz; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i == j) &#123;</span><br><span class="line">                    dp[j] = <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (s[i] == s[j]) &#123;</span><br><span class="line">                        dp[j] = tmp[j - <span class="number">1</span>] + <span class="number">2</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        dp[j] = <span class="built_in">max</span>(tmp[j], dp[j - <span class="number">1</span>]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            tmp = dp;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>超级回文数</p><p>如果一个正整数自身是回文数，而且它也是一个回文数的平方，那么我们称这个数为 <strong>超级回文数</strong> 。</p><p>现在，给你两个以字符串形式表示的正整数 left 和 right ，统计并返回区间 <code>[left, right]</code> 中的 <strong>超级回文数</strong> 的数目。</p><p>直接在 $10^{18}$ 的区间里找回文数无异于大海捞针，但构造 $10^9$ 以内的回文数非常快。</p><p>一个回文数 $R$ 可以由它的“前半部分”决定：</p><ul><li>如果 $R$ 的长度为 $L$，我们只需要枚举前 $\lceil L/2 \rceil$ 位数字。</li><li>例如，前缀 <code>123</code> 可以构造出：<ul><li>奇数长度回文：<code>12321</code></li><li>偶数长度回文：<code>123321</code></li></ul></li></ul><p>由于 $R \le 10^9$，它的前缀最大只需要到 $10^{4.5} \approx 31622$。实际上，我们只需要从 $1$ 枚举到 $10^5$ 左右，就能构造出所有的回文根 $R$。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">check</span><span class="params">(string&amp; x, <span class="type">long</span> <span class="type">long</span> left, <span class="type">long</span> <span class="type">long</span> right)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (x.<span class="built_in">size</span>() &gt;= <span class="number">10</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> num = <span class="built_in">stoll</span>(x);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> r = num * num;</span><br><span class="line">        <span class="keyword">if</span> (r &gt; right) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 验证根号值在范围内并且平方为回文</span></span><br><span class="line">        string s = <span class="built_in">to_string</span>(r);</span><br><span class="line">        <span class="keyword">if</span> (r &gt;= left &amp;&amp; <span class="built_in">validPalindrome</span>(s)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">validPalindrome</span><span class="params">(string&amp; s)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> l = <span class="number">0</span>, r = s.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (l &lt; r) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s[l] != s[r]) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            l++;</span><br><span class="line">            r--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">superpalindromesInRange</span><span class="params">(string left, string right)</span> </span>&#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> MAGIC = <span class="number">1e5</span>;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> l = <span class="built_in">stoll</span>(left);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> r = <span class="built_in">stoll</span>(right);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; MAGIC; i++) &#123;</span><br><span class="line">            string s = <span class="built_in">to_string</span>(i);</span><br><span class="line">            string rs = s;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = s.<span class="built_in">size</span>() - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">                rs += s[i];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">check</span>(rs, l, r)) &#123;</span><br><span class="line">                res++;</span><br><span class="line">            &#125;</span><br><span class="line">            rs = s;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = s.<span class="built_in">size</span>() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">                rs += s[i];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">check</span>(rs, l, r)) &#123;</span><br><span class="line">                res++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="游戏问题"><a href="#游戏问题" class="headerlink" title="游戏问题"></a>游戏问题</h3><p>给定一个长度为4的整数数组 <code>cards</code> 。你有 <code>4</code> 张卡片，每张卡片上都包含一个范围在 <code>[1,9]</code> 的数字。您应该使用运算符 <code>[&#39;+&#39;, &#39;-&#39;, &#39;*&#39;, &#39;/&#39;]</code> 和括号 <code>&#39;(&#39;</code> 和 <code>&#39;)&#39;</code> 将这些卡片上的数字排列成数学表达式，以获得值24。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">solve</span><span class="params">(vector&lt;<span class="type">double</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (nums.<span class="built_in">size</span>() == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">abs</span>(nums[<span class="number">0</span>] - <span class="number">24.0</span>) &lt; <span class="number">1e-6</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 选择其中两个数</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nums.<span class="built_in">size</span>(); j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i == j) &#123;</span><br><span class="line">                    <span class="comment">// 同一个数</span></span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                vector&lt;<span class="type">double</span>&gt; nextNums;</span><br><span class="line">                <span class="comment">// 剩下的数</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">0</span>; k &lt; nums.<span class="built_in">size</span>(); k++) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (k != i &amp;&amp; k != j) &#123;</span><br><span class="line">                        <span class="comment">// 选择剩余的数</span></span><br><span class="line">                        nextNums.<span class="built_in">push_back</span>(nums[k]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 对选择的两个数进行计算</span></span><br><span class="line">                <span class="type">double</span> n = nums[i] + nums[j];</span><br><span class="line">                nextNums.<span class="built_in">push_back</span>(n);</span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">solve</span>(nextNums)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                nextNums.<span class="built_in">pop_back</span>();</span><br><span class="line">                <span class="comment">// 减法</span></span><br><span class="line">                n = nums[i] - nums[j];</span><br><span class="line">                nextNums.<span class="built_in">push_back</span>(n);</span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">solve</span>(nextNums)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                nextNums.<span class="built_in">pop_back</span>();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 乘法</span></span><br><span class="line">                n = nums[i] * nums[j];</span><br><span class="line">                nextNums.<span class="built_in">push_back</span>(n);</span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">solve</span>(nextNums)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                nextNums.<span class="built_in">pop_back</span>();</span><br><span class="line">                <span class="comment">// 除法</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">abs</span>(nums[j]) &gt; <span class="number">1e-6</span>) &#123;</span><br><span class="line">                    n = nums[i] / nums[j];</span><br><span class="line">                    nextNums.<span class="built_in">push_back</span>(n);</span><br><span class="line">                    <span class="keyword">if</span> (<span class="built_in">solve</span>(nextNums)) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    nextNums.<span class="built_in">pop_back</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">judgePoint24</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; cards)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 4个数字选择其中两个 进行计算 直到只剩1个</span></span><br><span class="line">        <span class="comment">// 回溯/穷举</span></span><br><span class="line">        vector&lt;<span class="type">double</span>&gt; nums;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; n : cards) &#123;</span><br><span class="line">            nums.<span class="built_in">push_back</span>(<span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(n));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">solve</span>(nums);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>解数独</p><p>编写一个程序，通过填充空格来解决数独问题。</p><p>数独的解法需 <strong>遵循如下规则</strong>：</p><ol><li>数字 <code>1-9</code> 在每一行只能出现一次。</li><li>数字 <code>1-9</code> 在每一列只能出现一次。</li><li>数字 <code>1-9</code> 在每一个以粗实线分隔的 <code>3x3</code> 宫内只能出现一次。（请参考示例图）</li></ol><p>数独部分空格内已填入了数字，空白格用 <code>&#39;.&#39;</code> 表示</p><h2 id="广度优先和深度优先遍历"><a href="#广度优先和深度优先遍历" class="headerlink" title="广度优先和深度优先遍历"></a>广度优先和深度优先遍历</h2><p>据搜索方式的不同，搜索算法大致可以分为深度优先遍历（Depth First Search，DFS）和广度优先遍历（Breadth First Search，BFS）。</p><p>以树为例，DFS的思路是沿着子树尽可能深地搜索树的分支，到达叶子节点后通过回溯重复上述过程，直到所有的节点都被访问。BFS的思路则是一层一层地访问节点，直到完成遍历。由于DFS和BFS的这种差异，BFS一般用来求解最短问题（dijkstra算法的特例），而DFS书写起来比较简单，因此对于不是最短问题的情况，我们优先考虑使用DFS。然而事无绝对，DFS 也可以解决最短问题，但是要注意栈溢出的问题。在很多情况下，两者可以交替使用，比如本章要讲的岛屿问题。不管是DFS还是BFS，本质上都是搜索，而这样的搜索通常来说都是暴力搜索，因此当需要对问题的所有可能情况进行穷举时，我们就应该想到DFS和BFS。而第16章要讲解的回溯法，也是DFS的一种，即也是一种暴力搜索方法，只不过回溯法会涉及前进和回溯的过程。</p><p>使用DFS进行解题的大概思路是定义起始节点和结束节点，从起点开始不断深入其他节点，在搜索的过程中判断是否满足特定条件</p><p><img data-src="https://s2.loli.net/2026/02/02/bcyS78esmrTuiHl.png" alt="image-20260202132556224"></p><p>如果在树的题目中使用DFS，由于树是不存在环的，因此有关树的题目大多数不需要visited，但是如果对树的结构做了修改，使之出现了环，那就仍然需要visited</p><p>对于二叉树的题目，除了递归出口的条件，还会写一些其他的逻辑，这些逻辑由于位置的不同，产生的效果也截然不同。根据DFS逻辑位置的不同，我们将其分为三种类型，一种是自顶向下（前序遍历）的，一种是自底向上（后序遍历）的，最后一种是中序遍历。</p><p>大多数有关树的题目使用后序遍历会比较简单，并且大多需要依赖左/右子树的返回值。例如第1448题统计二叉树中好节点的数目。</p><p>●　也有一部分有关树的题目需要前序遍历，而前序遍历通常要结合参数扩展技巧。例如第1022题从根到叶的二进制数之和。</p><p>●　如果能使用参数和节点本身的值来决定应该传递给它的子节点的参数，那么就用前序遍历。</p><p>●　对于树中的任意一个节点，如果知道它子节点的答案，就能计算出当前节点的答案，那么就用后序遍历。●　如果遇到二叉搜索树，则考虑使用中序遍历。</p><p>相对于DFS来说，BFS的变种比较少，能解决的问题种类比较单一。BFS比较适合用来找最短距离，因此如果题目中提到了最短距离，首先应该想到使用BFS。使用BFS进行解题的思路同样是定义起始节点和结束节点，从起点开始不断深入其他节点，在搜索的过程中判断是否满足特定条件。BFS和DFS只是遍历的方向不同，即上面提到的DFS是尽可能深地搜索树的分支，而BFS则是一层一层地访问节点。队列可以帮我们实现“一层一层地访问节点”的效果。其本质就是不断访问邻居，把邻居逐个加入队列，根据队列先进先出的特点，把每一层节点访问完后，会继续访问下一层节点</p><p>路径之和</p><p>给你二叉树的根节点 <code>root</code> 和一个表示目标和的整数 <code>targetSum</code> 。判断该树中是否存在 <strong>根节点到叶子节点</strong> 的路径，这条路径上所有节点值相加等于目标和 <code>targetSum</code> 。如果存在，返回 <code>true</code> ；否则，返回 <code>false</code> 。</p><p><strong>叶子节点</strong> 是指没有子节点的节点。</p><p>一种直观的思路是自顶向下，使用前序遍历+参数扩展，在向下递归的同时更新参数，当到达叶子节点或空节点时判断是否满足条件。在这里，我们可以将目标和sum通过参数扩展的形式向下传递，在叶子节点上判断当前节点的val是否等于传递下来的参数sum。这是一种非常常见的DFS解题思路，除了前序遍历，还有一种常见的二叉树的深度遍历法是后序遍历，即在递归函数返回时对问题进行求解，使用子树的返回值来计算当前节点的返回值。通常来讲，DFS有递归和迭代两种实现方式。因为树结构天然具有递归的特性（子树性质和整个树性质一致），使用递归可以很容易地将整个树问题转换成子树问题。当我们层层递归到最小的子树时，这个最小子树的解（也被称为递归出口）往往很容易就能够得到，再一步步回溯就能得到原问题的解。小提示：树的题目，优先考虑使用DFS递归解决。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(TreeNode* node, <span class="type">int</span> targetSum)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (node-&gt;left == <span class="literal">nullptr</span> &amp;&amp; node-&gt;right == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (node-&gt;val == targetSum) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">bool</span> leftIsValid = <span class="built_in">dfs</span>(node-&gt;left, targetSum - node-&gt;val);</span><br><span class="line">        <span class="type">bool</span> rightIsValid = <span class="built_in">dfs</span>(node-&gt;right, targetSum - node-&gt;val);</span><br><span class="line">        <span class="keyword">if</span> (leftIsValid || rightIsValid) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">hasPathSum</span><span class="params">(TreeNode* root, <span class="type">int</span> targetSum)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 先序遍历DFS</span></span><br><span class="line">        <span class="comment">// 递归</span></span><br><span class="line">        <span class="comment">// return dfs(root, targetSum);</span></span><br><span class="line">        <span class="comment">// 迭代</span></span><br><span class="line">        <span class="keyword">if</span> (!root) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        stack&lt;pair&lt;TreeNode*, <span class="type">int</span>&gt;&gt; stk;</span><br><span class="line">        stk.<span class="built_in">push</span>(&#123;root, targetSum&#125;);</span><br><span class="line">        <span class="keyword">while</span> (!stk.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">auto</span> [node, target] = stk.<span class="built_in">top</span>();</span><br><span class="line">            stk.<span class="built_in">pop</span>();</span><br><span class="line">            <span class="keyword">if</span> (node-&gt;left == <span class="literal">nullptr</span> &amp;&amp; node-&gt;right == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (node-&gt;val == target) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (node-&gt;left) &#123;</span><br><span class="line">                stk.<span class="built_in">push</span>(&#123;node-&gt;left, target - node-&gt;val&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (node-&gt;right) &#123;</span><br><span class="line">                stk.<span class="built_in">push</span>(&#123;node-&gt;right, target - node-&gt;val&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>二叉树中的最大路径和</p><p>二叉树中的 <strong>路径</strong> 被定义为一条节点序列，序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 <strong>至多出现一次</strong> 。该路径 <strong>至少包含一个</strong> 节点，且不一定经过根节点</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> res&#123;INT_MIN&#125;;</span><br><span class="line">    <span class="comment">// 获得该节点的“贡献”</span></span><br><span class="line">    <span class="comment">// 也就是该节点为根节点/开始，的最大路径和</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(TreeNode* node)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 后序遍历</span></span><br><span class="line">        <span class="type">int</span> lVal = <span class="built_in">max</span>(<span class="number">0</span>, <span class="built_in">dfs</span>(node-&gt;left));</span><br><span class="line">        <span class="type">int</span> rVal = <span class="built_in">max</span>(<span class="number">0</span>, <span class="built_in">dfs</span>(node-&gt;right));</span><br><span class="line">        <span class="comment">// 计算每个节点贡献的值</span></span><br><span class="line">        <span class="type">int</span> nodeVal = <span class="built_in">max</span>(lVal, rVal) + node-&gt;val;</span><br><span class="line">        <span class="comment">// 更新最大值</span></span><br><span class="line">        res = <span class="built_in">max</span>(res, lVal + rVal + node-&gt;val);</span><br><span class="line">        <span class="keyword">return</span> nodeVal;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxPathSum</span><span class="params">(TreeNode* root)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 路径和的组成</span></span><br><span class="line">        <span class="comment">// 1.经过根节点以及左右子节点</span></span><br><span class="line">        <span class="comment">// 2.不经过根节点 左子树</span></span><br><span class="line">        <span class="comment">// 3.不经过根节点 右子树</span></span><br><span class="line">        <span class="comment">// 选取这其中的最大值</span></span><br><span class="line">        <span class="built_in">dfs</span>(root);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="岛屿问题"><a href="#岛屿问题" class="headerlink" title="岛屿问题"></a>岛屿问题</h3><p>给你一个大小为 <code>m x n</code> 的二维二进制网格 <code>grid</code> 。网格表示一个地图，其中，<code>0</code> 表示水，<code>1</code> 表示陆地。最初，<code>grid</code> 中的所有单元格都是水单元格（即，所有单元格都是 <code>0</code>）。</p><p>可以通过执行 <code>addLand</code> 操作，将某个位置的水转换成陆地。给你一个数组 <code>positions</code> ，其中 <code>positions[i] = [ri, ci]</code> 是要执行第 <code>i</code> 次操作的位置 <code>(ri, ci)</code> 。</p><p>返回一个整数数组 <code>answer</code> ，其中 <code>answer[i]</code> 是将单元格 <code>(ri, ci)</code> 转换为陆地后，地图中岛屿的数量。</p><p><strong>岛屿</strong> 的定义是被「水」包围的「陆地」，通过水平方向或者垂直方向上相邻的陆地连接而成。你可以假设地图网格的四边均被无边无际的「水」所包围。</p><p>需要动态地求出每次addLand操作之后的无向图中的连通分量。而求连通分量的数量的问题都可以通过DFS、BFS或并查集来解决。首先来看DFS和BFS，任何通过DFS和BFS来解决的图类问题都有一个前提：图是被预先处理好的。而本题中的图是动态变化的，因此这时用DFS或BFS来处理效率就不那么高了。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">UnionFind</span> &#123;</span><br><span class="line">    <span class="keyword">private</span>:</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; parent;</span><br><span class="line">        <span class="type">int</span> m;</span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="built_in">UnionFind</span>(<span class="type">int</span> m, <span class="type">int</span> n) : <span class="built_in">m</span>(m) &#123;</span><br><span class="line">            <span class="type">int</span> sz = m * n;</span><br><span class="line">            parent.<span class="built_in">resize</span>(sz);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">                parent[i] = <span class="number">-1</span>; <span class="comment">// 水</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="type">void</span> <span class="title">addLand</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>&#123;</span><br><span class="line">            <span class="type">int</span> p = x * m + y;</span><br><span class="line">            <span class="keyword">if</span> (parent[p] != <span class="number">-1</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果是陆地</span></span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            parent[p] = p; <span class="comment">// 设置值为本身位置(&gt;0)</span></span><br><span class="line">            cnt++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> p)</span> </span>&#123; <span class="keyword">return</span> parent[p] == p ? p : parent[p] = <span class="built_in">find</span>(p); &#125;</span><br><span class="line">        <span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> pos_x, <span class="type">int</span> pos_y)</span> </span>&#123;</span><br><span class="line">            <span class="type">int</span> p = pos_x * m + pos_y;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">find</span>(p);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="type">bool</span> <span class="title">isLand</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>&#123;</span><br><span class="line">            <span class="type">int</span> pos = x * m + y;</span><br><span class="line">            <span class="keyword">return</span> parent[pos] != <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function"><span class="type">int</span> <span class="title">getCount</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> cnt; &#125;</span><br><span class="line">        <span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">            <span class="type">int</span> px = <span class="built_in">find</span>(u);</span><br><span class="line">            <span class="type">int</span> py = <span class="built_in">find</span>(v);</span><br><span class="line">            <span class="keyword">if</span> (px == py) &#123;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            parent[py] = px;</span><br><span class="line">            cnt--;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; dirs = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">numIslands2</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; positions)</span> </span>&#123;</span><br><span class="line">        <span class="function">UnionFind <span class="title">uf</span><span class="params">(m, n)</span></span>;</span><br><span class="line">        <span class="type">int</span> sz = positions.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">res</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">int</span> x = positions[i][<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> y = positions[i][<span class="number">1</span>];</span><br><span class="line">            <span class="keyword">if</span> (uf.<span class="built_in">isLand</span>(x, y)) &#123;</span><br><span class="line">                <span class="comment">// 本身是陆地</span></span><br><span class="line">                res[i] = res[i - <span class="number">1</span>];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 是水</span></span><br><span class="line">                uf.<span class="built_in">addLand</span>(x, y);</span><br><span class="line">                <span class="comment">// 进行合并</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : dirs) &#123;</span><br><span class="line">                    <span class="type">int</span> nx = x + dir.first;</span><br><span class="line">                    <span class="type">int</span> ny = y + dir.second;</span><br><span class="line">                    <span class="comment">// 判断四个方向能否合并</span></span><br><span class="line">                    <span class="keyword">if</span> (nx &lt; <span class="number">0</span> || nx &gt;= m || ny &lt; <span class="number">0</span> || ny &gt;= n) &#123;</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (!uf.<span class="built_in">isLand</span>(nx, ny)) &#123;</span><br><span class="line">                        <span class="keyword">continue</span>; <span class="comment">// 如果是水 跳过</span></span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="type">int</span> pos = x * m + y;</span><br><span class="line">                    uf.<span class="built_in">join</span>(pos, nx * m + ny);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            res[i] = uf.<span class="built_in">getCount</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>DFS和BFS都属于树/图的搜索算法，两者在用于具体问题时各有优劣，具体如下。</p><p>●　求给定图中两点之间最短路径或检验图的二分性，使用BFS更优。</p><p>●　求无向图的连通分量数量，两者差不多。两者在实现过程中使用的基础数据结构也有区别。</p><p>在实际做题当中，一般使用栈来实现DFS，使用队列来实现BFS。另外，DFS和回溯算法之间的关系界线是模糊的，网上的说法也各不一样，在这里我们没必要过于纠结其精确的定义。对于DFS，另外一个知识点也是值得注意的。在二叉树中，DFS可以被分为前序遍历、中序遍历和后序遍历，并且引申出一系列相关题目。最后，本章的路径和问题、岛屿问题只详细讲述了两种算法的基本写法，而在实际的刷题过程中，我们可能会使用这两种基本写法的变种或延伸，比如运用双向搜索技巧、dijkstra 算法、A* 算法等</p><p>从前序和中序构建二叉树</p><p>给定两个整数数组 <code>preorder</code> 和 <code>inorder</code> ，其中 <code>preorder</code> 是二叉树的<strong>先序遍历</strong>， <code>inorder</code> 是同一棵树的<strong>中序遍历</strong>，请构造二叉树并返回其根节点。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Definition for a binary tree node.</span></span><br><span class="line"><span class="comment"> * struct TreeNode &#123;</span></span><br><span class="line"><span class="comment"> *     int val;</span></span><br><span class="line"><span class="comment"> *     TreeNode *left;</span></span><br><span class="line"><span class="comment"> *     TreeNode *right;</span></span><br><span class="line"><span class="comment"> *     TreeNode() : val(0), left(nullptr), right(nullptr) &#123;&#125;</span></span><br><span class="line"><span class="comment"> *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) &#123;&#125;</span></span><br><span class="line"><span class="comment"> *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),</span></span><br><span class="line"><span class="comment"> * right(right) &#123;&#125;</span></span><br><span class="line"><span class="comment"> * &#125;;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">TreeNode* <span class="title">buildNode</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; preorder, vector&lt;<span class="type">int</span>&gt;&amp; inorder,</span></span></span><br><span class="line"><span class="params"><span class="function">                        unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&amp; inOrderMap, <span class="type">int</span> leftIdxPre,</span></span></span><br><span class="line"><span class="params"><span class="function">                        <span class="type">int</span> rightIdxPre, <span class="type">int</span> leftIdxIn, <span class="type">int</span> rightIdxIn)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (leftIdxPre &gt; rightIdxPre) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 1.找到根节点</span></span><br><span class="line">        <span class="type">int</span> nodeVal = preorder[leftIdxPre];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.确定根节点在中序中位置</span></span><br><span class="line">        <span class="type">int</span> idxInOrder = inOrderMap[nodeVal];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3.确定左右子树范围</span></span><br><span class="line">        <span class="comment">// 左子树节点个数</span></span><br><span class="line">        <span class="type">int</span> leftNodeNum = idxInOrder - leftIdxIn;</span><br><span class="line">        <span class="comment">// 4. 递归处理</span></span><br><span class="line">        TreeNode* node = <span class="keyword">new</span> <span class="built_in">TreeNode</span>(nodeVal);</span><br><span class="line">        <span class="comment">// 在先序中 左节点开始的位置是leftIdxPre+1</span></span><br><span class="line">        node-&gt;left =</span><br><span class="line">            <span class="built_in">buildNode</span>(preorder, inorder, inOrderMap, leftIdxPre + <span class="number">1</span>,</span><br><span class="line">                      leftIdxPre + leftNodeNum, leftIdxIn, idxInOrder - <span class="number">1</span>);</span><br><span class="line">        node-&gt;right = <span class="built_in">buildNode</span>(preorder, inorder, inOrderMap,</span><br><span class="line">                                leftIdxPre + leftNodeNum + <span class="number">1</span>, rightIdxPre,</span><br><span class="line">                                idxInOrder + <span class="number">1</span>, rightIdxIn);</span><br><span class="line">        <span class="keyword">return</span> node;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">TreeNode* <span class="title">buildTree</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; preorder, vector&lt;<span class="type">int</span>&gt;&amp; inorder)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 先序遍历确定根节点</span></span><br><span class="line">        <span class="comment">// 中序遍历确定左右子树/子节点</span></span><br><span class="line">        unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt; inOrderMap;</span><br><span class="line">        <span class="type">int</span> sz = inorder.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            inOrderMap[inorder[i]] = i; <span class="comment">// 记录中序遍历节点位置</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">auto</span> node =</span><br><span class="line">            <span class="built_in">buildNode</span>(preorder, inorder, inOrderMap, <span class="number">0</span>, sz - <span class="number">1</span>, <span class="number">0</span>, sz - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> node;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>不同路径</p><p>一个机器人位于一个 <code>m x n</code> 网格的左上角 （起始点在下图中标记为 “Start” ）。</p><p>机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角（在下图中标记为 “Finish” ）。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">uniquePaths</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(m,<span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;n;i++) &#123;</span><br><span class="line">            dp[<span class="number">0</span>][i] = <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;m;i++) &#123;</span><br><span class="line">            dp[i][<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;m;i++) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>;j&lt;n;j++) &#123;</span><br><span class="line">                dp[i][j] = dp[i<span class="number">-1</span>][j] + dp[i][j<span class="number">-1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[m<span class="number">-1</span>][n<span class="number">-1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>不同路径I</p><p>给定一个 <code>m x n</code> 的整数数组 <code>grid</code>。一个机器人初始位于 <strong>左上角</strong>（即 <code>grid[0][0]</code>）。机器人尝试移动到 <strong>右下角</strong>（即 <code>grid[m - 1][n - 1]</code>）。机器人每次只能向下或者向右移动一步。</p><p>网格中的障碍物和空位置分别用 <code>1</code> 和 <code>0</code> 来表示。机器人的移动路径中不能包含 <strong>任何</strong> 有障碍物的方格。</p><p>返回机器人能够到达右下角的不同路径数量。</p><p>测试用例保证答案小于等于 <code>2 * 109</code>。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">uniquePathsWithObstacles</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; obstacleGrid)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划</span></span><br><span class="line">        <span class="type">int</span> m = obstacleGrid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = obstacleGrid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// dp[i][j]表示到达i,j的路径数目</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(m, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (obstacleGrid[<span class="number">0</span>][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                dp[<span class="number">0</span>][j] = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (obstacleGrid[i][<span class="number">0</span>] == <span class="number">0</span>) &#123;</span><br><span class="line">                dp[i][<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (obstacleGrid[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                dp[i][j] = dp[i - <span class="number">1</span>][j] + dp[i][j - <span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[m - <span class="number">1</span>][n - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>不同路径II</p><p>在二维网格 <code>grid</code> 上，有 4 种类型的方格：</p><ul><li><code>1</code> 表示起始方格。且只有一个起始方格。</li><li><code>2</code> 表示结束方格，且只有一个结束方格。</li><li><code>0</code> 表示我们可以走过的空方格。</li><li><code>-1</code> 表示我们无法跨越的障碍。</li></ul><p>返回在四个方向（上、下、左、右）上行走时，从起始方格到结束方格的不同路径的数目<strong>。</strong></p><p><strong>每一个无障碍方格都要通过一次，但是一条路径中不能重复通过同一个方格</strong>。</p><p>虽然题目名字叫“不同路径”，但它和普通的动态规划路径题完全不同，因为它有一个硬性约束：<strong>必须经过每一个无障碍方格（0）恰好一次</strong>。图论的角度来看，这实际上是在寻找网格图中的<strong>哈密顿路径（Hamiltonian Path）</strong>。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">    vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; dirs = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid, <span class="type">int</span> x, <span class="type">int</span> y, <span class="type">int</span> step,</span></span></span><br><span class="line"><span class="params"><span class="function">             <span class="type">int</span> targetStep)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = grid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 四个方向</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : dirs) &#123;</span><br><span class="line">            <span class="type">int</span> nx = x + dir.first;</span><br><span class="line">            <span class="type">int</span> ny = y + dir.second;</span><br><span class="line">            <span class="keyword">if</span> (nx &lt; <span class="number">0</span> || nx &gt;= m || ny &lt; <span class="number">0</span> || ny &gt;= n) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (grid[nx][ny] == <span class="number">-1</span> || grid[nx][ny] == <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="comment">// 不能到达</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (grid[nx][ny] == <span class="number">2</span>) &#123;</span><br><span class="line">                <span class="comment">// 到达终点且必须都走一次</span></span><br><span class="line">                <span class="keyword">if</span> (step == targetStep) &#123;</span><br><span class="line">                    res++;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 不能重复走</span></span><br><span class="line">            grid[nx][ny] = <span class="number">-1</span>;</span><br><span class="line">            <span class="built_in">dfs</span>(grid, nx, ny, step + <span class="number">1</span>, targetStep);</span><br><span class="line">            grid[nx][ny] = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">uniquePathsIII</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// dfs</span></span><br><span class="line">        <span class="type">int</span> m = grid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 记录0的个数</span></span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> start_x&#123;&#125;, start_y&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                    cnt++;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    start_x = i;</span><br><span class="line">                    start_y = j;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 四个方向走</span></span><br><span class="line">        <span class="built_in">dfs</span>(grid, start_x, start_y, <span class="number">0</span>, cnt);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol><li>DFS 回溯 (Backtracking)：试错的探险家</li></ol><p>回溯的核心是<strong>“尝试 -&gt; 撤销”</strong>。它不关心过去是否算过这个点，它关心的是当前的<strong>路径</strong>。</p><ul><li><strong>核心逻辑</strong>：走不通就退回来，把标记抹掉，换条路再试。</li><li><strong>状态依赖</strong>：当前的状态通常依赖于<strong>路径历史</strong>（比如你走过的路，别人就不能再走了）。</li><li><strong>空间复杂度</strong>：通常较小，只取决于递归深度。</li><li><strong>时间复杂度</strong>：通常是指数级的，比如 $O(2^n)$ 或 $O(n!)$。</li></ul><ol><li>记忆化搜索 (Memoization)：聪明的收纳狂</li></ol><p>记忆化搜索本质上是<strong>“自顶向下的动态规划（DP）”</strong>。它的核心是<strong>“查表 -&gt; 存储”</strong>。</p><ul><li><strong>核心逻辑</strong>：如果这个子问题我以前算过，直接把结果扔给你，绝不浪费时间重算。</li><li><strong>状态依赖</strong>：当前的状态只取决于<strong>当前的参数</strong>，与你是怎么走到这一步的（路径）无关。这就是所谓的“无后效性”。</li><li><strong>空间复杂度</strong>：较大，需要额外的空间（哈希表或数组）来存储中间结果。</li><li><strong>时间复杂度</strong>：通常能将指数级降低到多项式级，如 $O(n^2)$。</li></ul><p>如果在 DFS 递归函数的末尾看到了类似这样的代码：</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">grid[x][y] = <span class="number">0</span>; <span class="comment">// 恢复现场</span></span><br><span class="line"><span class="keyword">return</span> res;</span><br></pre></td></tr></table></figure><p> 这通常是 <strong>回溯</strong>。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (memo[state] != <span class="number">-1</span>) <span class="keyword">return</span> memo[state]; <span class="comment">// 查表</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">return</span> memo[state] = res; <span class="comment">// 存表</span></span><br></pre></td></tr></table></figure><p> 这一定是 <strong>记忆化搜索</strong>。</p><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>记忆化搜索 (Top-down)</strong></th><th><strong>动态规划 (Bottom-up)</strong></th></tr></thead><tbody><tr><td><strong>实现方式</strong></td><td><strong>递归 + 缓存</strong> (通常是哈希表或数组)</td><td><strong>迭代 + 递推表</strong> (通常是数组)</td></tr><tr><td><strong>思维方向</strong></td><td><strong>自顶向下</strong>：从大问题拆解成小问题</td><td><strong>自底向上</strong>：从小问题推导出大问题</td></tr><tr><td><strong>计算顺序</strong></td><td>依赖驱动：只计算到达目标所需的子状态</td><td>顺序驱动：按预定顺序计算所有可能状态</td></tr><tr><td><strong>空间开销</strong></td><td>缓存空间 + <strong>递归调用栈</strong> (容易溢出)</td><td>缓存空间 (通常可以通过滚动数组优化)</td></tr><tr><td><strong>适用场景</strong></td><td>状态空间稀疏、转移方程复杂</td><td>状态空间密集、需要极致性能优化</td></tr></tbody></table></div><p>公式：$f(n) = f(n-1) + f(n-2)$</p><p><strong>记忆化搜索版</strong></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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> memo[<span class="number">1001</span>]; <span class="comment">// 初始化为 -1</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">fib</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="keyword">if</span> (memo[n] != <span class="number">-1</span>) <span class="keyword">return</span> memo[n]; <span class="comment">// 查表</span></span><br><span class="line">    <span class="keyword">return</span> memo[n] = <span class="built_in">fib</span>(n - <span class="number">1</span>) + <span class="built_in">fib</span>(n - <span class="number">2</span>); <span class="comment">// 存表</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>动态规划版</strong></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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">fib</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">0</span>; dp[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        dp[i] = dp[i - <span class="number">1</span>] + dp[i - <span class="number">2</span>]; <span class="comment">// 顺序填表</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>统计二叉树中好节点数目</p><p>给你一棵根为 <code>root</code> 的二叉树，请你返回二叉树中好节点的数目。</p><p>「好节点」X 定义为：从根到该节点 X 所经过的节点中，没有任何节点的值大于 X 的值。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Definition for a binary tree node.</span></span><br><span class="line"><span class="comment"> * struct TreeNode &#123;</span></span><br><span class="line"><span class="comment"> *     int val;</span></span><br><span class="line"><span class="comment"> *     TreeNode *left;</span></span><br><span class="line"><span class="comment"> *     TreeNode *right;</span></span><br><span class="line"><span class="comment"> *     TreeNode() : val(0), left(nullptr), right(nullptr) &#123;&#125;</span></span><br><span class="line"><span class="comment"> *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) &#123;&#125;</span></span><br><span class="line"><span class="comment"> *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),</span></span><br><span class="line"><span class="comment"> * right(right) &#123;&#125;</span></span><br><span class="line"><span class="comment"> * &#125;;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(TreeNode* node, <span class="type">int</span> max_val)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 判断一个节点是好节点</span></span><br><span class="line">        <span class="comment">// 从根节点到当前节点的值均小于等于当前节点的值</span></span><br><span class="line">        <span class="comment">// 记录路径中的最大值判断是否小于等于当前值</span></span><br><span class="line">        max_val = <span class="built_in">max</span>(max_val, node-&gt;val);</span><br><span class="line">        <span class="keyword">if</span> (max_val &lt;= node-&gt;val) &#123;</span><br><span class="line">            res++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">dfs</span>(node-&gt;left, max_val);</span><br><span class="line">        <span class="built_in">dfs</span>(node-&gt;right, max_val);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">goodNodes</span><span class="params">(TreeNode* root)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">dfs</span>(root, INT_MIN);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>矩阵中最长递增路径</p><p>定一个 <code>m x n</code> 整数矩阵 <code>matrix</code> ，找出其中 <strong>最长递增路径</strong> 的长度。</p><p>对于每个单元格，你可以往上，下，左，右四个方向移动。 你 <strong>不能</strong> 在 <strong>对角线</strong> 方向上移动或移动到 <strong>边界外</strong>（即不允许环绕）。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> ans&#123;<span class="number">1</span>&#125;;</span><br><span class="line">    vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; dirs = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix, <span class="type">int</span> x, <span class="type">int</span> y,</span></span></span><br><span class="line"><span class="params"><span class="function">            vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; memo)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo[x][y] != <span class="number">-1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[x][y];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 还未访问过</span></span><br><span class="line">        <span class="type">int</span> val&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : dirs) &#123;</span><br><span class="line">            <span class="type">int</span> nx = x + dir.first;</span><br><span class="line">            <span class="type">int</span> ny = y + dir.second;</span><br><span class="line">            <span class="keyword">if</span> (nx &lt; <span class="number">0</span> || nx &gt;= m || ny &lt; <span class="number">0</span> || ny &gt;= n) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (matrix[nx][ny] &lt; matrix[x][y]) &#123;</span><br><span class="line">                val = <span class="built_in">max</span>(val, <span class="number">1</span> + <span class="built_in">dfs</span>(matrix, nx, ny, memo));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[x][y] = val;</span><br><span class="line">        ans = <span class="built_in">max</span>(ans, val);</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">longestIncreasingPath</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 反向来,从一个位置向周围四个方向走</span></span><br><span class="line">        <span class="comment">// 直到走到最小值,记录该路径的最长递增路径</span></span><br><span class="line">        <span class="comment">// 重复处理时记忆化</span></span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">memo</span>(m, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n, <span class="number">-1</span>));</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="built_in">dfs</span>(matrix, i, j, memo);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="博弈论题目"><a href="#博弈论题目" class="headerlink" title="博弈论题目"></a>博弈论题目</h4><p>Nim游戏</p><p>你和你的朋友，两个人一起玩 <a href="https://baike.baidu.com/item/Nim游戏/6737105">Nim 游戏</a>：</p><ul><li>桌子上有一堆石头。</li><li>你们轮流进行自己的回合， <strong>你作为先手</strong> 。</li><li>每一回合，轮到的人拿掉 1 - 3 块石头。</li><li>拿掉最后一块石头的人就是获胜者。</li></ul><p>假设你们每一步都是最优解。请编写一个函数，来判断你是否可以在给定石头数量为 <code>n</code> 的情况下赢得游戏。如果可以赢，返回 <code>true</code>；否则，返回 <code>false</code> 。</p><p><strong>我能赢吗</strong></p><p>在 “100 game” 这个游戏中，两名玩家轮流选择从 <code>1</code> 到 <code>10</code> 的任意整数，累计整数和，先使得累计整数和 <strong>达到或超过</strong> 100 的玩家，即为胜者。</p><p>如果我们将游戏规则改为 “玩家 <strong>不能</strong> 重复使用整数” 呢？</p><p>例如，两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数（不放回），直到累计整数和 &gt;= 100。</p><p>给定两个整数 <code>maxChoosableInteger</code> （整数池中可选择的最大数）和 <code>desiredTotal</code>（累计和），若先出手的玩家能稳赢则返回 <code>true</code> ，否则返回 <code>false</code> 。假设两位玩家游戏时都表现 <strong>最佳</strong> 。</p><p>博弈类题目通常很难写出迭代式的 DP，但用<strong>记忆化搜索</strong>配合<strong>状态压缩（Bitmask）</strong>则非常直观。</p><p>考虑边界情况，当所有数字选完仍无法到达 desiredTotal 时，两人都无法获胜，返回 false。当所有数字的和大于等于 desiredTotal 时，其中一方能获得胜利，需要通过搜索来判断获胜方。</p><p>在游戏中途，假设已经被使用的数字的集合为 usedNumbers，这些数字的和为 currentTotal。当某方行动时，如果他能在未选择的数字中选出一个 i，使得 i+currentTotal≥desiredTotal，则他能获胜。否则，需要继续通过搜索来判断获胜方。在剩下的数字中，如果他能选择一个 i，使得对方在接下来的局面中无法获胜，则他会获胜。否则，他会失败。</p><p>根据这个思想设计搜索函数 dfs，其中 usedNumbers 可以用一个整数来表示，从低位到高位，第 i 位为 1 则表示数字 i 已经被使用，为 0 则表示数字 i 未被使用。如果当前玩家获胜，则返回 true，否则返回 false。为了避免重复计算，需要使用记忆化的操作来降低时间复杂度</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// memo[mask] == 0: 未计算</span></span><br><span class="line">    <span class="comment">// memo[mask] == 1: 必胜 (true)</span></span><br><span class="line">    <span class="comment">// memo[mask] == 2: 必败 (false)</span></span><br><span class="line">    <span class="type">int</span> memo[<span class="number">1</span> &lt;&lt; <span class="number">21</span>]; </span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">canIWin</span><span class="params">(<span class="type">int</span> maxChoosableInteger, <span class="type">int</span> desiredTotal)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 特判 1：如果所有数字之和都达不到目标，谁都赢不了</span></span><br><span class="line">        <span class="type">int</span> sum = (<span class="number">1</span> + maxChoosableInteger) * maxChoosableInteger / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (sum &lt; desiredTotal) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 特判 2：如果最大的数直接能达到目标，先手秒胜</span></span><br><span class="line">        <span class="keyword">if</span> (desiredTotal &lt;= <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(maxChoosableInteger, desiredTotal, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> maxInt, <span class="type">int</span> total, <span class="type">int</span> mask)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 查表：如果算过这个状态，直接返回</span></span><br><span class="line">        <span class="keyword">if</span> (memo[mask] != <span class="number">0</span>) <span class="keyword">return</span> memo[mask] == <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 尝试选择每一个还没被选过的数字</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= maxInt; i++) &#123;</span><br><span class="line">            <span class="type">int</span> bit = <span class="number">1</span> &lt;&lt; i;</span><br><span class="line">            <span class="keyword">if</span> (!(mask &amp; bit)) &#123; <span class="comment">// 如果数字 i 还没被选</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 1. 如果选了 i 直接达到目标，我赢了</span></span><br><span class="line">                <span class="comment">// 2. 或者选了 i 之后，递归下去对方会输 (dfs 返回 false)</span></span><br><span class="line">                <span class="keyword">if</span> (total - i &lt;= <span class="number">0</span> || !<span class="built_in">dfs</span>(maxInt, total - i, mask | bit)) &#123;</span><br><span class="line">                    memo[mask] = <span class="number">1</span>; <span class="comment">// 记录必胜</span></span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        memo[mask] = <span class="number">2</span>; <span class="comment">// 所有尝试都失败了，我必败</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a href="https://leetcode.cn/problems/guess-number-higher-or-lower-ii/">猜数字大小 II</a></p><p>我们正在玩一个猜数游戏，游戏规则如下：</p><ol><li>我从 <code>1</code> 到 <code>n</code> 之间选择一个数字。</li><li>你来猜我选了哪个数字。</li><li>如果你猜到正确的数字，就会 <strong>赢得游戏</strong> 。</li><li>如果你猜错了，那么我会告诉你，我选的数字比你的 <strong>更大或者更小</strong> ，并且你需要继续猜数。</li><li>每当你猜了数字 <code>x</code> 并且猜错了的时候，你需要支付金额为 <code>x</code> 的现金。如果你花光了钱，就会 <strong>输掉游戏</strong> 。</li></ol><p>给你一个特定的数字 <code>n</code> ，返回能够 <strong>确保你获胜</strong> 的最小现金数，<strong>不管我选择那个数字</strong></p><p><img data-src="https://s2.loli.net/2026/02/03/bhlRSygsmIWnpoV.png" alt="image-20260203160641411"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getMoneyAmount</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// dp[i][j] = k+max(dp[i][k-1],dp[k+1][j])</span></span><br><span class="line">        <span class="comment">// dp[i][j]表示在[i,j]范围内获胜的最小现金数</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">1</span>));</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = n<span class="number">-1</span>;i&gt;=<span class="number">1</span>;i--) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = i<span class="number">+1</span>;j&lt;=n;j++) &#123;</span><br><span class="line">                dp[i][j] = dp[i][j<span class="number">-1</span>] + j;</span><br><span class="line">                <span class="keyword">for</span>(<span class="type">int</span> k = i;k&lt;j;k++) &#123;</span><br><span class="line">                    dp[i][j] = <span class="built_in">min</span>(dp[i][j],k+<span class="built_in">max</span>(dp[i][k<span class="number">-1</span>],dp[k<span class="number">+1</span>][j]));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">1</span>][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>翻转游戏</p><p>你和朋友玩一个叫做「翻转游戏」的游戏。游戏规则如下：</p><p>给你一个字符串 <code>currentState</code> ，其中只含 <code>&#39;+&#39;</code> 和 <code>&#39;-&#39;</code> 。你和朋友轮流将 <strong>连续</strong> 的两个 <code>&quot;++&quot;</code> 反转成 <code>&quot;--&quot;</code> 。当一方无法进行有效的翻转时便意味着游戏结束，则另一方获胜。</p><p>计算并返回 <strong>一次有效操作</strong> 后，字符串 <code>currentState</code> 所有的可能状态，返回结果可以按 <strong>任意顺序</strong> 排列。如果不存在可能的有效操作，请返回一个空列表 <code>[]</code> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">generatePossibleNextMoves</span><span class="params">(string currentState)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 对连续的++或者--进行翻转</span></span><br><span class="line">        vector&lt;string&gt; res;</span><br><span class="line">        <span class="type">int</span> start_idx&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (start_idx &lt; currentState.<span class="built_in">size</span>() - <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (currentState[start_idx] == currentState[start_idx + <span class="number">1</span>]) &#123;</span><br><span class="line">                <span class="comment">// 翻转</span></span><br><span class="line">                string s = currentState;</span><br><span class="line">                <span class="keyword">if</span> (currentState[start_idx] == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">                    s[start_idx] = <span class="string">&#x27;-&#x27;</span>;</span><br><span class="line">                    s[start_idx + <span class="number">1</span>] = <span class="string">&#x27;-&#x27;</span>;</span><br><span class="line">                    res.<span class="built_in">push_back</span>(s);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            start_idx++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>翻转游戏II</p><p>你和朋友玩一个叫做「翻转游戏」的游戏。游戏规则如下：</p><p>给你一个字符串 currentState ，其中只含 ‘+’ 和 ‘-‘ 。你和朋友轮流将 连续 的两个 “++” 反转成 “—“ 。当一方无法进行有效的翻转时便意味着游戏结束，则另一方获胜。默认每个人都会采取最优策略。</p><p>请你写出一个函数来判定起始玩家 是否存在必胜的方案 ：如果存在，返回 true ；否则，返回 false 。</p><p>在博弈问题中，我们通常使用<strong>递归</strong>来模拟每一轮的操作。</p><ul><li><strong>必胜态 (Winning State)</strong>：从当前状态出发，存在至少一种移动方式，能够进入“必败态”。</li><li><strong>必败态 (Losing State)</strong>：从当前状态出发，无论做出什么移动，下个状态都是“必胜态”；或者根本无法移动。</li></ul><p>算法步骤：</p><ol><li><strong>遍历字符串</strong>：寻找所有连续的 <code>++</code>。</li><li><strong>模拟翻转</strong>：将当前的 <code>++</code> 替换为 <code>--</code>，得到一个新的字符串。</li><li><strong>递归判断</strong>：调用函数判断<strong>对手</strong>在面对新字符串时是否会输掉。如果对手输了（返回 <code>false</code>），说明我们找到了一个必胜点，直接返回 <code>true</code>。</li><li><strong>记忆化 (Memoization)</strong>：为了避免重复计算（同一个字符串可能通过不同的翻转路径达到），我们使用哈希表记录已经计算过的状态。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(string&amp; currentState, unordered_map&lt;string, <span class="type">bool</span>&gt;&amp; memo)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(currentState)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[currentState];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// DFS</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; currentState.<span class="built_in">size</span>() - <span class="number">1</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (currentState[i] == currentState[i + <span class="number">1</span>]) &#123;</span><br><span class="line">                <span class="keyword">if</span> (currentState[i] == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">                    string nextState = currentState;</span><br><span class="line">                    nextState[i] = <span class="string">&#x27;-&#x27;</span>;</span><br><span class="line">                    nextState[i + <span class="number">1</span>] = <span class="string">&#x27;-&#x27;</span>;</span><br><span class="line">                    <span class="type">bool</span> flag = <span class="built_in">dfs</span>(nextState, memo);</span><br><span class="line">                    <span class="keyword">if</span> (!flag) &#123;</span><br><span class="line">                        <span class="comment">// 对方必输</span></span><br><span class="line">                        memo[currentState] = <span class="literal">true</span>;</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[currentState] = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">canWin</span><span class="params">(string currentState)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划/记忆化搜索</span></span><br><span class="line">        <span class="comment">// 递推</span></span><br><span class="line">        <span class="comment">// 记忆化搜索</span></span><br><span class="line">        unordered_map&lt;string, <span class="type">bool</span>&gt; memo;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(currentState, memo);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>对于 这类博弈题，<strong>递归 + 记忆化</strong> 是通杀方案。只要记住“我的胜利建立在对手的绝望之上”这个博弈原则，逻辑就能顺理成章。</p><p>求出硬币游戏赢家</p><p>给你两个 <strong>正</strong> 整数 <code>x</code> 和 <code>y</code> ，分别表示价值为 75 和 10 的硬币的数目。</p><p>Alice 和 Bob 正在玩一个游戏。每一轮中，Alice 先进行操作，Bob 后操作。每次操作中，玩家需要拿走价值 <strong>总和</strong> 为 115 的硬币。如果一名玩家无法执行此操作，那么这名玩家 <strong>输掉</strong> 游戏。</p><p>两名玩家都采取 <strong>最优</strong> 策略，请你返回游戏的赢家。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">winningPlayer</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 每次x-1,y-4</span></span><br><span class="line">        <span class="comment">// 直到为0</span></span><br><span class="line">        <span class="type">int</span> ops = <span class="built_in">min</span>(x, y / <span class="number">4</span>);</span><br><span class="line">        <span class="keyword">return</span> ops % <span class="number">2</span> ? <span class="string">&quot;Alice&quot;</span> : <span class="string">&quot;Bob&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>预测赢家</p><p>给一个整数数组 <code>nums</code> 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。</p><p>玩家 1 和玩家 2 轮流进行自己的回合，玩家 1 先手。开始时，两个玩家的初始分值都是 <code>0</code> 。每一回合，玩家从数组的任意一端取一个数字（即，<code>nums[0]</code> 或 <code>nums[nums.length - 1]</code>），取到的数字将会从数组中移除（数组长度减 <code>1</code> ）。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时，游戏结束。</p><p>如果玩家 1 能成为赢家，返回 <code>true</code> 。如果两个玩家得分相等，同样认为玩家 1 是游戏的赢家，也返回 <code>true</code> 。你可以假设每个玩家的玩法都会使他的分数最大化。</p><p>在处理这类“两个人都采取最优策略”的问题时，我们不要去管玩家 1 拿了多少分、玩家 2 拿了多少分。我们只关心一个值：<strong>当前玩家相对于对手的“净胜分”</strong>。</p><p>定义函数 $f(i, j)$：表示在数组 <code>nums</code> 从索引 $i$ 到 $j$ 的这段区间内，当前走棋的人能拿到的<strong>最大净分数</strong>（即：我的得分 - 对手的得分）。</p><ul><li><strong>如果你选左端点 <code>nums[i]</code></strong>：你得到了 <code>nums[i]</code> 分，剩下的区间是 $[i+1, j]$。在剩下的区间里，对手会作为先手，他能拿到的最大净分值是 $f(i+1, j)$。所以你的净胜分就是 <code>nums[i] - f(i+1, j)</code>。</li><li><strong>如果你选右端点 <code>nums[j]</code></strong>：同理，你的净胜分就是 <code>nums[j] - f(i, j-1)</code>。</li></ul><p>作为大师级玩家，你当然会在这两种选择中选那个<strong>更大的</strong>。</p><ol><li>递归逻辑与状态转移</li></ol><script type="math/tex; mode=display">f(i, j) = \max(\text{nums}[i] - f(i+1, j), \text{nums}[j] - f(i, j-1))</script><p><strong>基准情况 (Base Case)</strong>：</p><p>当 $i == j$ 时，只剩一个数字，当前玩家直接拿走，净胜分为 $f(i, i) = \text{nums}[i]$。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">max_diff</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> i, <span class="type">int</span> j)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (j == i) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 净分数</span></span><br><span class="line">        <span class="type">int</span> l = nums[i] - <span class="built_in">max_diff</span>(nums, i + <span class="number">1</span>, j);</span><br><span class="line">        <span class="type">int</span> r = nums[j] - <span class="built_in">max_diff</span>(nums, i, j - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(l, r);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">predictTheWinner</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max_diff</span>(nums, <span class="number">0</span>, nums.<span class="built_in">size</span>() - <span class="number">1</span>) &gt;= <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// 动态规划</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">PredictTheWinner</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> length = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">auto</span> dp = vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; (length, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(length));</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">            dp[i][i] = nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = length - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i + <span class="number">1</span>; j &lt; length; j++) &#123;</span><br><span class="line">                dp[i][j] = <span class="built_in">max</span>(nums[i] - dp[i + <span class="number">1</span>][j], nums[j] - dp[i][j - <span class="number">1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">0</span>][length - <span class="number">1</span>] &gt;= <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>求出胜利玩家数目</p><p>给你一个整数 <code>n</code> ，表示在一个游戏中的玩家数目。同时给你一个二维整数数组 <code>pick</code> ，其中 <code>pick[i] = [xi, yi]</code> 表示玩家 <code>xi</code> 获得了一个颜色为 <code>yi</code> 的球。</p><p>如果玩家 <code>i</code> 获得的球中任何一种颜色球的数目 <strong>严格大于</strong> <code>i</code> 个，那么我们说玩家 <code>i</code> 是胜利玩家。换句话说：</p><ul><li>如果玩家 0 获得了任何的球，那么玩家 0 是胜利玩家。</li><li>如果玩家 1 获得了至少 2 个相同颜色的球，那么玩家 1 是胜利玩家。</li><li>…</li><li>如果玩家 <code>i</code> 获得了至少 <code>i + 1</code> 个相同颜色的球，那么玩家 <code>i</code> 是胜利玩家。</li></ul><p>请你返回游戏中 <strong>胜利玩家</strong> 的数目。</p><p><strong>注意</strong>，可能有多个玩家是胜利玩家。</p><p>石子游戏</p><p>Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子，<strong>排成一行</strong>；每堆都有 <strong>正</strong> 整数颗石子，数目为 <code>piles[i]</code> 。</p><p>游戏以谁手中的石子最多来决出胜负。石子的 <strong>总数</strong> 是 <strong>奇数</strong> ，所以没有平局。</p><p>Alice 和 Bob 轮流进行，<strong>Alice 先开始</strong> 。 每回合，玩家从行的 <strong>开始</strong> 或 <strong>结束</strong> 处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止，此时手中 <strong>石子最多</strong> 的玩家 <strong>获胜</strong> 。</p><p>假设 Alice 和 Bob 都发挥出最佳水平，当 Alice 赢得比赛时返回 <code>true</code> ，当 Bob 赢得比赛时返回 <code>false</code></p><p>永远为true</p><p>单词拆分</p><p>给你一个字符串 <code>s</code> 和一个字符串列表 <code>wordDict</code> 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 <code>s</code> 则返回 <code>true</code>。</p><p><strong>注意：</strong>不要求字典中出现的单词全部都使用，并且字典中的单词可以重复使用。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">wordBreak</span><span class="params">(string s, vector&lt;string&gt;&amp; wordDict)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">wordSet</span><span class="params">(wordDict.begin(), wordDict.end())</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">dp</span><span class="params">(sz + <span class="number">1</span>)</span></span>;</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 状态转移方程</span></span><br><span class="line">        <span class="comment">// dp[i]表示前i个字符能否被表示</span></span><br><span class="line">        <span class="comment">// dp[j] = dp[j-len]|word[j-len:j]</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= sz; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                <span class="comment">// 字符串起点 j-(i-1)</span></span><br><span class="line">                string sstr = s.<span class="built_in">substr</span>(j, i - j);</span><br><span class="line">                <span class="keyword">if</span> (wordSet.<span class="built_in">count</span>(sstr) &amp;&amp; dp[j]) &#123;</span><br><span class="line">                    dp[i] = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, <span class="type">bool</span>&gt; memo;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(string&amp; s, <span class="type">int</span> start_idx, unordered_set&lt;string&gt; wordSet)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(start_idx)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[start_idx];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (start_idx == s.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = start_idx; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            string tmp = s.<span class="built_in">substr</span>(start_idx, i - start_idx + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">if</span> (wordSet.<span class="built_in">count</span>(tmp) &amp;&amp; <span class="built_in">dfs</span>(s, i + <span class="number">1</span>, wordSet)) &#123;</span><br><span class="line">                memo[start_idx] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[start_idx] = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">wordBreak</span><span class="params">(string s, vector&lt;string&gt;&amp; wordDict)</span> </span>&#123;</span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">wordSet</span><span class="params">(wordDict.begin(), wordDict.end())</span></span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(s, <span class="number">0</span>, wordSet);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>单词拆分II</p><p>给定一个字符串 <code>s</code> 和一个字符串字典 <code>wordDict</code> ，在字符串 <code>s</code> 中增加空格来构建一个句子，使得句子中所有的单词都在词典中。<strong>以任意顺序</strong> 返回所有这些可能的句子。</p><p><strong>注意：</strong>词典中的同一个单词可能在分段中被重复使用多次。</p><p>将大问题拆解为：<strong>“当前单词 + 剩余子串的所有拆分可能”</strong>。</p><ul><li><strong>定义函数</strong> <code>dfs(start)</code>：返回字符串 <code>s[start:]</code> 能够组成的所有合法句子列表。</li><li><strong>递归过程</strong>：<ol><li>从 <code>start</code> 开始，尝试所有可能的结尾 <code>end</code>。</li><li>如果 <code>s[start:end]</code> 是一个单词：<ul><li>递归调用 <code>dfs(end)</code> 获取后缀的所有组合。</li><li>将当前单词与后缀的每一个组合用空格连接。</li></ul></li><li><strong>记忆化</strong>：使用字典 <code>memo</code> 记录 <code>start</code> 对应的所有结果。如果下次再遇到相同的 <code>start</code>，直接返回结果，不再重复搜索。</li></ol></li></ul><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><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_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 记忆化搜索 存储结果</span></span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, vector&lt;string&gt;&gt; memo;</span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">dfs</span><span class="params">(string&amp; s, unordered_set&lt;string&gt;&amp; uset, <span class="type">int</span> start)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(start)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[start];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (start == s.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="comment">// 到末尾</span></span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;&quot;</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;string&gt; ans;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = start; j &lt; s.<span class="built_in">size</span>(); j++) &#123;</span><br><span class="line">            string sstr = s.<span class="built_in">substr</span>(start, j - start + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">if</span> (uset.<span class="built_in">count</span>(sstr)) &#123;</span><br><span class="line">                <span class="comment">// 获得后缀</span></span><br><span class="line">                <span class="keyword">auto</span> res = <span class="built_in">dfs</span>(s, uset, j + <span class="number">1</span>);</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; s : res) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (s.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">                        ans.<span class="built_in">push_back</span>(sstr);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        ans.<span class="built_in">push_back</span>(sstr + <span class="string">&quot; &quot;</span> + s);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[start] = ans;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">wordBreak</span><span class="params">(string s, vector&lt;string&gt;&amp; wordDict)</span> </span>&#123;</span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">uset</span><span class="params">(wordDict.begin(), wordDict.end())</span></span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(s, uset, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>划分为k个相等的子集</p><p>给定一个整数数组 <code>nums</code> 和一个正整数 <code>k</code>，找出是否有可能把这个数组分成 <code>k</code> 个非空子集，其总和都相等。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, <span class="type">bool</span>&gt; memo;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> usedNums, <span class="type">int</span> curSum, <span class="type">int</span> targetSum,</span></span></span><br><span class="line"><span class="params"><span class="function">             <span class="type">int</span> cnt, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(usedNums)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[usedNums];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (cnt == k) &#123;</span><br><span class="line">            memo[usedNums] = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (targetSum == curSum) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">dfs</span>(nums, usedNums, <span class="number">0</span>, targetSum, cnt + <span class="number">1</span>, k)) &#123;</span><br><span class="line">                memo[usedNums] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (usedNums &amp; (<span class="number">1</span> &lt;&lt; i)) &#123;</span><br><span class="line">                <span class="comment">// 已经用过</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (nums[i] + curSum &gt; targetSum) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (nums[i] + curSum &lt;= targetSum) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">dfs</span>(nums, usedNums | (<span class="number">1</span> &lt;&lt; i), nums[i] + curSum, targetSum,</span><br><span class="line">                        cnt, k)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[usedNums] = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">canPartitionKSubsets</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sum_val = <span class="built_in">accumulate</span>(nums.<span class="built_in">begin</span>(), nums.<span class="built_in">end</span>(), <span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 不能整除</span></span><br><span class="line">        <span class="keyword">if</span> (sum_val % k) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> target = sum_val / k;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(nums, <span class="number">0</span>, <span class="number">0</span>, target, <span class="number">0</span>, k);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>戳气球</p><p>有 <code>n</code> 个气球，编号为<code>0</code> 到 <code>n - 1</code>，每个气球上都标有一个数字，这些数字存在数组 <code>nums</code> 中。</p><p>现在要求你戳破所有的气球。戳破第 <code>i</code> 个气球，你可以获得 <code>nums[i - 1] * nums[i] * nums[i + 1]</code> 枚硬币。 这里的 <code>i - 1</code> 和 <code>i + 1</code> 代表和 <code>i</code> 相邻的两个气球的序号。如果 <code>i - 1</code>或 <code>i + 1</code> 超出了数组的边界，那么就当它是一个数字为 <code>1</code> 的气球。</p><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxCoins</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 1 xxxx 1</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">coins</span><span class="params">(n + <span class="number">2</span>, <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            coins[i] = nums[i - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// dp[i][j]表示在(i,j)范围也就是[i+1,j-1]范围的最大硬币数</span></span><br><span class="line">        <span class="comment">// k在[i+1,j-1]内，表示(i,j)内最后一个戳破的气球</span></span><br><span class="line">        <span class="comment">// dp[i][j] = dp[i][k]*dp[k][j]nums[i]*nums[k]*nums[j]</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n + <span class="number">2</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">2</span>));</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = n + <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i + <span class="number">2</span>; j &lt;= n + <span class="number">1</span>; j++) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> k = i + <span class="number">1</span>; k &lt; j; k++) &#123;</span><br><span class="line">                    dp[i][j] =</span><br><span class="line">                        <span class="built_in">max</span>(dp[i][k] + dp[k][j] + coins[i] * coins[j] * coins[k],</span><br><span class="line">                            dp[i][j]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">0</span>][n + <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">// 记忆化搜索</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; rec;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; val;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">solve</span><span class="params">(<span class="type">int</span> left, <span class="type">int</span> right)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (left &gt;= right - <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (rec[left][right] != <span class="number">-1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> rec[left][right];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = left + <span class="number">1</span>; i &lt; right; i++) &#123;</span><br><span class="line">            <span class="type">int</span> sum = val[left] * val[i] * val[right];</span><br><span class="line">            sum += <span class="built_in">solve</span>(left, i) + <span class="built_in">solve</span>(i, right);</span><br><span class="line">            rec[left][right] = <span class="built_in">max</span>(rec[left][right], sum);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> rec[left][right];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxCoins</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">        val.<span class="built_in">resize</span>(n + <span class="number">2</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            val[i] = nums[i - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        val[<span class="number">0</span>] = val[n + <span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">        rec.<span class="built_in">resize</span>(n + <span class="number">2</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">2</span>, <span class="number">-1</span>));</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">solve</span>(<span class="number">0</span>, n + <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通配符匹配</p><p>给你一个输入字符串 (<code>s</code>) 和一个字符模式 (<code>p</code>) ，请你实现一个支持 <code>&#39;?&#39;</code> 和 <code>&#39;*&#39;</code> 匹配规则的通配符匹配：</p><ul><li><code>&#39;?&#39;</code> 可以匹配任何单个字符。</li><li><code>&#39;*&#39;</code> 可以匹配任意字符序列（包括空字符序列）。</li></ul><p>判定匹配成功的充要条件是：字符模式必须能够 <strong>完全匹配</strong> 输入字符串（而不是部分匹配</p><p>定义 $dp[i][j]$ 为 $s$ 的前 $i$ 个字符和 $p$ 的前 $j$ 个字符是否匹配。</p><p>情况 A：<code>p[j-1]</code> 是普通字符或 <code>?</code></p><p>如果 <code>s[i-1] == p[j-1]</code> 或者 <code>p[j-1] == &#39;?&#39;</code>，匹配 1 对 1。</p><script type="math/tex; mode=display">dp[i][j] = dp[i-1][j-1]</script><p>情况 B：<code>p[j-1]</code> 是 <code>*</code></p><p>这时候 <code>*</code> 有两种选择：</p><ol><li><p><strong>当成空字符串（匹配 0 个）</strong>：直接看模式串前一个位置是否匹配当前字符串。</p><script type="math/tex; mode=display">dp[i][j] = dp[i][j-1]</script></li><li><p><strong>匹配 1 个或多个字符</strong>：既然 <code>*</code> 能匹配任意序列，只要 $s$ 的前一个字符已经和当前 <code>*</code> 匹配上了，那么当前字符也能被这个 <code>*</code> 吸收。</p><script type="math/tex; mode=display">dp[i][j] = dp[i-1][j]</script></li></ol><p><strong>综合逻辑</strong>：$dp[i][j] = dp[i][j-1] \lor dp[i-1][j]$</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isMatch</span><span class="params">(string s, string p)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划</span></span><br><span class="line">        <span class="type">int</span> n1 = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n2 = p.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">bool</span>&gt;&gt; <span class="built_in">dp</span>(n1 + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">bool</span>&gt;(n2 + <span class="number">1</span>));</span><br><span class="line">        dp[<span class="number">0</span>][<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 状态转移方程</span></span><br><span class="line">        <span class="comment">// p[j] = &#x27;*&#x27; 可以匹配任意序列</span></span><br><span class="line">        <span class="comment">// dp[i][j] = dp[i-1][j]</span></span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n2; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (p[i - <span class="number">1</span>] == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                dp[<span class="number">0</span>][i] = dp[<span class="number">0</span>][i - <span class="number">1</span>];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                dp[<span class="number">0</span>][i] = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n1; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n2; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (p[j - <span class="number">1</span>] == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                    <span class="comment">// 匹配0个          或者            匹配多个</span></span><br><span class="line">                    dp[i][j] = dp[i][j - <span class="number">1</span>] || dp[i - <span class="number">1</span>][j];</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (p[j - <span class="number">1</span>] == <span class="string">&#x27;?&#x27;</span> || p[j - <span class="number">1</span>] == s[i - <span class="number">1</span>]) &#123;</span><br><span class="line">                    dp[i][j] = dp[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[n1][n2];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>正则表达式匹配</p><p>给你一个字符串 <code>s</code> 和一个字符规律 <code>p</code>，请你来实现一个支持 <code>&#39;.&#39;</code> 和 <code>&#39;*&#39;</code> 的正则表达式匹配。</p><ul><li><code>&#39;.&#39;</code> 匹配任意单个字符</li><li><code>&#39;*&#39;</code> 匹配零个或多个前面的那一个元素</li></ul><p>所谓匹配，是要涵盖 <strong>整个</strong> 字符串 <code>s</code> 的，而不是部分字符串。</p><p> 难点在于 <code>*</code> 的处理：它不是独立存在的，而是必须和<strong>前一个字符</strong>绑在一起看，表示“0 个或多个前面的那个元素”。这种“回头看”的逻辑让匹配变得非常复杂。</p><p>遍历 <code>i</code>（从 0 到 $m$）和 <code>j</code>（从 1 到 $n$），对于每个 <code>dp[i][j]</code>：</p><p>情况 A：<code>p[j-1]</code> 不是 <code>*</code></p><p>如果当前字符匹配（<code>s[i-1] == p[j-1]</code> 或 <code>p[j-1] == &#39;.&#39;</code>），则：</p><script type="math/tex; mode=display">dp[i][j] = dp[i-1][j-1]</script><p>情况 B：<code>p[j-1]</code> 是 <code>*</code></p><p>此时模式串中 <code>*</code> 前面的字符是 <code>p[j-2]</code>。</p><ol><li><p><strong>匹配 0 次</strong>：无论如何，我们可以选择忽略 <code>x*</code>。</p><script type="math/tex; mode=display">dp[i][j] = dp[i][j-2]</script></li><li><p><strong>匹配 1 次或多次</strong>：如果 <code>s[i-1]</code> 能匹配 <code>p[j-2]</code>，则我们可以“吃掉” <code>s</code> 的当前字符，并保持模式串位置不变。</p><script type="math/tex; mode=display">dp[i][j] = dp[i][j] \text{ 或 } dp[i-1][j]</script></li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isMatch</span><span class="params">(string s, string p)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz1 = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> sz2 = p.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">bool</span>&gt;&gt; <span class="built_in">dp</span>(sz1 + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">bool</span>&gt;(sz2 + <span class="number">1</span>));</span><br><span class="line">        dp[<span class="number">0</span>][<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 初始化dp</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">2</span>; j &lt;= sz2; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (p[j - <span class="number">1</span>] == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                dp[<span class="number">0</span>][j] = dp[<span class="number">0</span>][j - <span class="number">2</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= sz1; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= sz2; j++) &#123;</span><br><span class="line">                <span class="comment">// 判断更新dp情况</span></span><br><span class="line">                <span class="comment">// 如果前一个字符相等</span></span><br><span class="line">                <span class="comment">// cb a*</span></span><br><span class="line">                <span class="keyword">if</span> (p[j - <span class="number">1</span>] == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                    <span class="comment">// 匹配0个</span></span><br><span class="line">                    dp[i][j] = dp[i][j - <span class="number">2</span>];</span><br><span class="line">                    <span class="type">bool</span> is_match =</span><br><span class="line">                        ((s[i - <span class="number">1</span>] == p[j - <span class="number">2</span>]) || (p[j - <span class="number">2</span>] == <span class="string">&#x27;.&#x27;</span>));</span><br><span class="line">                    <span class="keyword">if</span> (is_match) &#123;</span><br><span class="line">                        <span class="comment">// 如果前一个字符匹配</span></span><br><span class="line">                        <span class="comment">// ab a*c</span></span><br><span class="line">                        dp[i][j] = (dp[i - <span class="number">1</span>][j] || dp[i][j]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 正常匹配</span></span><br><span class="line">                    <span class="keyword">if</span> (s[i - <span class="number">1</span>] == p[j - <span class="number">1</span>] || p[j - <span class="number">1</span>] == <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">                        dp[i][j] = dp[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz1][sz2];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>记忆化搜索题目</strong></p><ol><li>矩阵与路径类（最直观的应用）</li></ol><p>这类问题通常具有明确的方向性，递归路径清晰，但存在大量重复计算。</p><ul><li><strong><a href="https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/">329. 矩阵中的最长递增路径</a> (Hard)</strong><ul><li><strong>核心点</strong>：从任意点出发找最长路径。如果不用记忆化，DFS 会呈指数级增长。由于递增的限制，路径不会成环，非常适合递归缓存结果。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/unique-paths/">62. 不同路径</a> (Medium)</strong><ul><li><strong>核心点</strong>：虽然入门选手机通常用迭代 DP，但用递归+记忆化实现逻辑最自然。</li></ul></li></ul><ol><li>博弈论类（必须用记忆化）</li></ol><p>正如你刚才问的“翻转游戏”，博弈题几乎是记忆化搜索的本命题。</p><ul><li><strong><a href="https://leetcode.cn/problems/can-i-win/">464. 我能赢吗</a> (Medium)</strong><ul><li><strong>核心点</strong>：状态压缩 + 记忆化。需要记录哪些数字被选过（用二进制位表示），并判断当前玩家是否必胜。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/predict-the-winner/">486. 预测赢家</a> (Medium)</strong><ul><li><strong>核心点</strong>：经典的从数组两端取数的博弈，递归逻辑是 <code>max(左端取数 - 剩下的递归结果, 右端取数 - 剩下的递归结果)</code>。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/stone-game/">877. 石子游戏</a> (Medium)</strong><ul><li><strong>核心点</strong>：虽然数学推导必胜，但作为算法练习，它是典型的区间记忆化搜索。</li></ul></li></ul><ol><li>拆分与区间类（Range DP）</li></ol><p>将一个大问题拆解成多个小区间，再合并结果。</p><ul><li><strong><a href="https://leetcode.cn/problems/burst-balloons/">312. 戳气球</a> (Hard)</strong><ul><li><strong>核心点</strong>：区间 DP 的巅峰之作。自顶向下的记忆化搜索比自底向上的三层循环更容易理解：<code>solve(left, right)</code> 表示戳破 <code>(left, right)</code> 之间所有气球的最大收益。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/word-break/">139. 单词拆分</a> (Medium)</strong><ul><li><strong>核心点</strong>：判断字符串是否能由字典组成。递归判断 <code>s[i:]</code> 是否合法，并用 <code>memo</code> 记录。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/word-break-ii/">140. 单词拆分 II</a> (Hard)</strong><ul><li><strong>核心点</strong>：不仅要判断，还要返回所有路径。由于要构造大量字符串，记忆化搜索几乎是唯一解法。</li></ul></li></ul><ol><li>字符串匹配类</li></ol><ul><li><strong><a href="https://leetcode.cn/problems/regular-expression-matching/">10. 正则表达式匹配</a> (Hard)</strong><ul><li><strong>核心点</strong>：处理 <code>*</code> 和 <code>.</code> 的复杂逻辑。记忆化搜索可以让你专注处理当前的字符匹配，而不用纠结 DP 表的初始化。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/wildcard-matching/">44. 通配符匹配</a> (Hard)</strong><ul><li><strong>核心点</strong>：逻辑与 10 题类似，是练习递归思维的佳作。</li></ul></li></ul><ol><li>状态压缩类（进阶必备）</li></ol><p>当状态无法简单用数组下标表示时，通常配合位运算。</p><ul><li><strong><a href="https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/">698. 划分为k个相等的子集</a> (Medium)</strong><ul><li><strong>核心点</strong>：搜索过程中的状态（哪些元素已用过）通过位掩码记录在 <code>memo</code> 中。</li></ul></li></ul><p>当你发现题目有以下特征时，请优先考虑记忆化搜索：</p><ol><li><p><strong>状态转移方向“不规律”或“跳跃”</strong>：</p><p>比如在博弈类题目（如之前的“翻转游戏”）中，一个状态下一步可能跳到任何地方，你很难写出一个简单的 <code>for</code> 循环顺序。</p></li><li><p><strong>状态空间很大，但实际访问的状态很少（稀疏性）</strong>：</p><p>如果你开了一个 $1000 \times 1000$ 的 DP 表，但题目实际上只需要计算其中的几十个格子，用循环（DP）会浪费大量时间去填那些没用的格子。</p></li><li><p><strong>递归逻辑更符合直觉</strong>：</p><p>比如“单词拆分”或者“组合总和”，这种“拆解大问题”的思维用递归写起来非常顺手。</p><p>什么时候选“动态规划”？（For Loop + DP Table）</p></li></ol><p>当你发现题目有以下特征时，动态规划更香：</p><ol><li><p><strong>状态转移极其规律</strong>：</p><p>比如“爬楼梯”、“路径和”、“打家劫舍”。可以清晰地看到第 <code>i</code> 步只依赖 <code>i-1</code> 和 <code>i-2</code>。</p></li><li><p><strong>需要极致的空间优化</strong>：</p><p>如果你发现 <code>dp[i]</code> 只依赖 <code>dp[i-1]</code>，你可以用<strong>滚动数组</strong>把 $O(n)$ 空间压缩到 $O(1)$。记忆化搜索（因为有递归栈）很难做这种优化。</p></li><li><p><strong>避免递归深度限制</strong>：</p><p>在某些语言（如 Python/C++）中，如果递归太深（比如几万层），会导致栈溢出（Stack Overflow）。这时必须用循环（DP）。</p></li></ol><p>用rand7实现rand10</p><p>给定方法 <code>rand7</code> 可生成 <code>[1,7]</code> 范围内的均匀随机整数，试写一个方法 <code>rand10</code> 生成 <code>[1,10]</code> 范围内的均匀随机整数。</p><p>你只能调用 <code>rand7()</code> 且不能调用其他方法。请不要使用系统的 <code>Math.random()</code> 方法。</p><p>每个测试用例将有一个内部参数 <code>n</code>，即你实现的函数 <code>rand10()</code> 在测试时将被调用的次数。请注意，这不是传递给 <code>rand10()</code> 的参数。</p><h2 id="二分法"><a href="#二分法" class="headerlink" title="二分法"></a>二分法</h2><p>二分法是一种常用的算法，主要包括原始二分查找及实现难度更大的二分变种。<strong>二分法是分治思想的体现，它与分治法的区别在于分治法是将一个复杂的问题不断分解成几个规模更小的子问题，直至子问题可以直接求解</strong>；而二分法则是<strong>不断地通过比较操作将问题规模缩小一半，直至找到目标元素</strong></p><p>搜索旋转排序数组</p><p>整数数组 <code>nums</code> 按升序排列，数组中的值 <strong>互不相同</strong> 。</p><p>在传递给函数之前，<code>nums</code> 在预先未知的某个下标 <code>k</code>（<code>0 &lt;= k &lt; nums.length</code>）上进行了 <strong>向左旋转</strong>，使数组变为 <code>[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]</code>（下标 <strong>从 0 开始</strong> 计数）。例如， <code>[0,1,2,4,5,6,7]</code> 下标 <code>3</code> 上向左旋转后可能变为 <code>[4,5,6,7,0,1,2]</code> 。</p><p>给你 <strong>旋转后</strong> 的数组 <code>nums</code> 和一个整数 <code>target</code> ，如果 <code>nums</code> 中存在这个目标值 <code>target</code> ，则返回它的下标，否则返回 <code>-1</code> 。你必须设计一个时间复杂度为 <code>O(log n)</code> 的算法解决此问题。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">search</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 二分法</span></span><br><span class="line">        <span class="comment">// 左侧值均大于右侧值</span></span><br><span class="line">        <span class="comment">// 通过nums[mid]比较nums[right]</span></span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>, right = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (target == nums[mid]) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[left] &gt; nums[mid]) &#123;</span><br><span class="line">                <span class="comment">// mid-right单增</span></span><br><span class="line">                <span class="keyword">if</span> (target &gt; nums[mid] &amp;&amp; target &lt;= nums[right]) &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// left-mid单增</span></span><br><span class="line">                <span class="keyword">if</span> (target &gt;= nums[left] &amp;&amp; target &lt; nums[mid]) &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>寻找旋转排序数组中的最小值</p><p>已知一个长度为 <code>n</code> 的数组，预先按照升序排列，经由 <code>1</code> 到 <code>n</code> 次 <strong>旋转</strong> 后，得到输入数组。例如，原数组 <code>nums = [0,1,2,4,5,6,7]</code> 在变化后可能得到：</p><ul><li>若旋转 <code>4</code> 次，则可以得到 <code>[4,5,6,7,0,1,2]</code></li><li>若旋转 <code>7</code> 次，则可以得到 <code>[0,1,2,4,5,6,7]</code></li></ul><p>注意，数组 <code>[a[0], a[1], a[2], ..., a[n-1]]</code> <strong>旋转一次</strong> 的结果为数组 <code>[a[n-1], a[0], a[1], a[2], ..., a[n-2]]</code> 。</p><p>给你一个元素值 <strong>互不相同</strong> 的数组 <code>nums</code> ，它原来是一个升序排列的数组，并按上述情形进行了多次旋转。请你找出并返回数组中的 <strong>最小元素</strong> 。</p><p>查询的中间元素变得不确定，以往能够通过直接比对判断某个元素是否符合题目要求，而现在缺少直接判断的条件（mid==target）。幸运的是，还是能够通过二分法不断地缩小最终答案可能存在的区间，当区间只剩下一个元素时（l==h），那么它就是最终答案。二分法中有一种类型是查找最左（最右）满足条件的值，这也运用了类似的思想，即在找到满足条件的一个候选答案时，不是直接返回，而是贪心地继续查看是否还有其他答案。例如要在一个数组[1,2,2,3,4]中找最左边的等于2的值，当我们找到索引值为2的项时，不能直接返回，而是继续贪心地搜索区间，将右边的区间舍弃并继续查看左侧是否还有另外一个2</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findMin</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 旋转数组</span></span><br><span class="line">        <span class="comment">//  左侧值均大于右侧</span></span><br><span class="line">        <span class="comment">// 两侧都是增加</span></span><br><span class="line">        <span class="comment">// 最小值的左右两侧值均大于它</span></span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>, right = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="keyword">if</span> (left == right) &#123;</span><br><span class="line">                <span class="keyword">return</span> nums[left];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &lt; nums[right]) &#123;</span><br><span class="line">                right = mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findMin</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> low = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> high = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (low &lt; high) &#123;</span><br><span class="line">            <span class="type">int</span> pivot = low + (high - low) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (nums[pivot] &lt; nums[high]) &#123;</span><br><span class="line">                high = pivot;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                low = pivot + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nums[low];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>搜索旋转排序II</strong></p><p>已知存在一个按非降序排列的整数数组 <code>nums</code> ，数组中的值不必互不相同。</p><p>在传递给函数之前，<code>nums</code> 在预先未知的某个下标 <code>k</code>（<code>0 &lt;= k &lt; nums.length</code>）上进行了 <strong>旋转</strong> ，使数组变为 <code>[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]</code>（下标 <strong>从 0 开始</strong> 计数）。例如， <code>[0,1,2,4,4,4,5,6,6,7]</code> 在下标 <code>5</code> 处经旋转后可能变为 <code>[4,5,6,6,7,0,1,2,4,4]</code> 。</p><p>给你 <strong>旋转后</strong> 的数组 <code>nums</code> 和一个整数 <code>target</code> ，请你编写一个函数来判断给定的目标值是否存在于数组中。如果 <code>nums</code> 中存在这个目标值 <code>target</code> ，则返回 <code>true</code> ，否则返回 <code>false</code> 。</p><p>你必须尽可能减少整个操作步骤。</p><p>在上一题中，通过 <code>nums[left] &lt;= nums[mid]</code> 就能确切地判断左半部分是否有序。但在有重复的情况下，如果 <strong><code>nums[left] == nums[mid]</code></strong>，我们就无法判断旋转点到底在左边还是右边。</p><p>为了解决这个问题，当我们遇到 <code>nums[left] == nums[mid]</code> 时，我们不能简单地排除一半区间，但我们可以确定：<strong>既然 <code>nums[mid]</code> 不是我们要找的 <code>target</code>，那么当前的 <code>left</code> 也是多余的。</strong></p><ul><li><strong>对策：</strong> 执行 <code>left++</code>，缩小范围，然后进入下一次二分判定。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">search</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>, right = nums.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] == target) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] == nums[left]) &#123;</span><br><span class="line">                left++;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &lt; nums[left]) &#123;</span><br><span class="line">                <span class="comment">// mid-right单增</span></span><br><span class="line">                <span class="keyword">if</span> (target &gt; nums[mid] &amp;&amp; target &lt;= nums[right]) &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (target &gt;= nums[left] &amp;&amp; target &lt; nums[mid]) &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>爱吃香蕉的珂珂</p><p>珂珂喜欢吃香蕉。这里有 <code>n</code> 堆香蕉，第 <code>i</code> 堆中有 <code>piles[i]</code> 根香蕉。警卫已经离开了，将在 <code>h</code> 小时后回来。</p><p>珂珂可以决定她吃香蕉的速度 <code>k</code> （单位：根/小时）。每个小时，她将会选择一堆香蕉，从中吃掉 <code>k</code> 根。如果这堆香蕉少于 <code>k</code> 根，她将吃掉这堆的所有香蕉，然后这一小时内不会再吃更多的香蕉。 </p><p>珂珂喜欢慢慢吃，但仍然想在警卫回来前吃掉所有的香蕉。</p><p>返回她可以在 <code>h</code> 小时内吃掉所有香蕉的最小速度 <code>k</code>（<code>k</code> 为整数）。</p><p>由于每小时最多只能吃一堆香蕉，速度最多达到最大堆的数量即可，因此，速度的范围为[1,max(piles)]，也就是答案一定在这个范围内。</p><p>道题可以看作在[1,max(piles)]中查找一个元素k。因此一个简单的思路是枚举从1到max(piles)的所有速度，并判断是否可以吃完，返回最早能够吃完的速度即可。注意在从1到max(piles)进行枚举的过程中，速度是单调递增变化的，这很容易让我们联想到前面的题目。具体来说，当我们在[l,h]中判断中间的速度mid是否可行时，有如下可能。●　mid不可行，则速度不够快，最小速度位于[mid+1,h]中，更新左边界l为mid+1。</p><p>●　mid可行，则可能的最小速度小于或等于mid，最小速度位于[l,mid]中，mid可能是最终答案，但不能直接排除，更新右边界h为mid。</p><p>●　l等于h，则查找区间只剩下一个l，最小速度等于l。因此这道题就是找到最小的可以吃完的速度，也就是上一节提到的最左满足条件的值的题目类型</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; piles, <span class="type">int</span> m, <span class="type">int</span> h)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 速度为m,看能否在h小时内吃完</span></span><br><span class="line">        <span class="type">long</span> <span class="type">long</span>  cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; p : piles) &#123;</span><br><span class="line">            <span class="comment">// cnt += ceil(double(p) / m);</span></span><br><span class="line">            cnt += (p + m - <span class="number">1</span>) / m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt &lt;= h;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minEatingSpeed</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; piles, <span class="type">int</span> h)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 最大最小值</span></span><br><span class="line">        <span class="comment">// 二分</span></span><br><span class="line">        <span class="type">int</span> max_val = *<span class="built_in">max_element</span>(piles.<span class="built_in">begin</span>(), piles.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> l = <span class="number">1</span>, r = max_val;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> m = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(piles, m, h)) &#123;</span><br><span class="line">                res = m;</span><br><span class="line">                r = m - <span class="number">1</span>; <span class="comment">// 必须为m-1,否则可能死循环</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                l = m + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>类似的题目</p><p>给你一个 <strong>下标从 0 开始</strong> 的整数数组 <code>candies</code> 。数组中的每个元素表示大小为 <code>candies[i]</code> 的一堆糖果。你可以将每堆糖果分成任意数量的 <strong>子堆</strong> ，但 <strong>无法</strong> 再将两堆合并到一起。</p><p>另给你一个整数 <code>k</code> 。你需要将这些糖果分配给 <code>k</code> 个小孩，使每个小孩分到 <strong>相同</strong> 数量的糖果。每个小孩可以拿走 <strong>至多一堆</strong> 糖果，有些糖果可能会不被分配。</p><p>返回每个小孩可以拿走的 <strong>最大糖果数目</strong> 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; candies, <span class="type">int</span> m, <span class="type">long</span> <span class="type">long</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (m == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; c : candies) &#123;</span><br><span class="line">            cnt += c / m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt &gt;= k;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maximumCandies</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; candies, <span class="type">long</span> <span class="type">long</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 每个小孩拿到的糖果数量(1,min(candies[i]))</span></span><br><span class="line">        <span class="comment">//</span></span><br><span class="line">        <span class="type">int</span> l = <span class="number">0</span>, r = *<span class="built_in">max_element</span>(candies.<span class="built_in">begin</span>(), candies.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> m = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(candies, m, k)) &#123;</span><br><span class="line">                l = m + <span class="number">1</span>;</span><br><span class="line">                res = m;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                r = m - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你一个整数 <code>n</code> ，表示有 <code>n</code> 间零售商店。总共有 <code>m</code> 种商品，每种商品的数目用一个下标从 <strong>0</strong> 开始的整数数组 <code>quantities</code> 表示，其中 <code>quantities[i]</code> 表示第 <code>i</code> 种商品的数目。</p><p>你需要将 <strong>所有商品</strong> 分配到零售商店，并遵守这些规则：</p><ul><li>一间商店 <strong>至多</strong> 只能有 <strong>一种商品</strong> ，但一间商店拥有的商品数目可以为 <strong>任意</strong> 件。</li><li>分配后，每间商店都会被分配一定数目的商品（可能为 <code>0</code> 件）。用 <code>x</code> 表示所有商店中分配商品数目的最大值，你希望 <code>x</code> 越小越好。也就是说，你想 <strong>最小化</strong> 分配给任意商店商品数目的 <strong>最大值</strong> 。</li></ul><p>请你返回最小的可能的 <code>x</code> 。</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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; quantities, <span class="type">int</span> m, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 满足条件</span></span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; q : quantities) &#123;</span><br><span class="line">            cnt += (q + m - <span class="number">1</span>) / m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt &lt;= n;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minimizedMaximum</span><span class="params">(<span class="type">int</span> n, vector&lt;<span class="type">int</span>&gt;&amp; quantities)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> l = <span class="number">1</span>, r = *<span class="built_in">max_element</span>(quantities.<span class="built_in">begin</span>(), quantities.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(quantities, mid, n)) &#123;</span><br><span class="line">                r = mid - <span class="number">1</span>;</span><br><span class="line">                ans = mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                l = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你一个整数数组 <code>ranks</code> ，表示一些机械工的 <strong>能力值</strong> 。<code>ranksi</code> 是第 <code>i</code> 位机械工的能力值。能力值为 <code>r</code> 的机械工可以在 <code>r * n2</code> 分钟内修好 <code>n</code> 辆车。</p><p>同时给你一个整数 <code>cars</code> ，表示总共需要修理的汽车数目。</p><p>请你返回修理所有汽车 <strong>最少</strong> 需要多少时间。</p><p><strong>注意：</strong>所有机械工可以同时修理汽车。</p><p><strong>寻找峰值</strong></p><p>峰值元素是指其值严格大于左右相邻值的元素。</p><p>给你一个整数数组 <code>nums</code>，找到峰值元素并返回其索引。数组可能包含多个峰值，在这种情况下，返回 <strong>任何一个峰值</strong> 所在位置即可。</p><p>你可以假设 <code>nums[-1] = nums[n] = -∞</code> 。你必须实现时间复杂度为 <code>O(log n)</code> 的算法来解决此问题。</p><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><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><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findPeakElement</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> l = <span class="number">0</span>, r = sz - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> m = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="type">int</span> n = nums[m];</span><br><span class="line">            <span class="keyword">if</span> ((m + <span class="number">1</span> &lt; sz) &amp;&amp; nums[m + <span class="number">1</span>] &gt; n) &#123;</span><br><span class="line">                <span class="comment">// 右侧值可能是峰值</span></span><br><span class="line">                l = m + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (m != <span class="number">0</span> &amp;&amp; nums[m - <span class="number">1</span>] &gt; n) &#123;</span><br><span class="line">                r = m - <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> m;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="type">int</span> <span class="title">findPeakElement</span><span class="params">(<span class="type">int</span>[] nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &gt; nums[mid + <span class="number">1</span>]) &#123;</span><br><span class="line">                <span class="comment">// 中间元素大于右边元素，说明峰值在左边（包括mid）</span></span><br><span class="line">                right = mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 中间元素小于右边元素，说明峰值在右边</span></span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> left;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>寻找峰值II</strong></p><p>一个 2D 网格中的 <strong>峰值</strong> 是指那些 <strong>严格大于</strong> 其相邻格子(上、下、左、右)的元素。</p><p>给你一个 <strong>从 0 开始编号</strong> 的 <code>m x n</code> 矩阵 <code>mat</code> ，其中任意两个相邻格子的值都 <strong>不相同</strong> 。找出 <strong>任意一个 峰值</strong> <code>mat[i][j]</code> 并 <strong>返回其位置</strong> <code>[i,j]</code> 。</p><p>你可以假设整个矩阵周边环绕着一圈值为 <code>-1</code> 的格子。</p><p>要求必须写出时间复杂度为 <code>O(m log(n))</code> 或 <code>O(n log(m))</code> 的算法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">findPeakGrid</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; mat)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = mat.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = mat[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> l =<span class="number">0</span>, r = m - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> i = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="type">int</span> j = <span class="built_in">max_element</span>(mat[i].<span class="built_in">begin</span>(), mat[i].<span class="built_in">end</span>()) - mat[i].<span class="built_in">begin</span>();</span><br><span class="line">            <span class="keyword">if</span> ((i &gt;= <span class="number">1</span>) &amp;&amp; mat[i][j] &lt; mat[i - <span class="number">1</span>][j]) &#123;</span><br><span class="line">                r = i - <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> ((i &lt; m - <span class="number">1</span>) &amp;&amp; mat[i][j] &lt; mat[i + <span class="number">1</span>][j]) &#123;</span><br><span class="line">                l = i + <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> &#123;i, j&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>分割数组的最大值</strong></p><p>给定一个非负整数数组 <code>nums</code> 和一个整数 <code>k</code> ，你需要将这个数组分成 <code>k</code> 个非空的连续子数组，使得这 <code>k</code> 个子数组各自和的最大值 <strong>最小</strong>。返回分割后最小的和的最大值。<strong>子数组</strong> 是数组中连续的部分。</p><p>假设这个和为 $X$：</p><ul><li>如果我们可以把数组分成 $\le k$ 个子数组，且每个子数组的和都不超过 $X$，说明 $X$ <strong>可能太大了</strong>，我们可以尝试更小的 $X$。</li><li>如果我们无论如何都要分成超过 $k$ 个子数组才能保证每组和 $\le X$，说明 $X$ <strong>太小了</strong>，必须调大。</li></ul><p>确定二分的边界：</p><ul><li><strong>左边界 (<code>left</code>)</strong>：数组中的<strong>最大值</strong>。因为每个数都要属于一个子数组，最大值所在的组之和至少就是它自己。</li><li><strong>右边界 (<code>right</code>)</strong>：数组中<strong>所有元素之和</strong>。对应 $k=1$ 的极端情况。</li></ul><p>对于给定的目标值 <code>mid</code>，我们如何判断 $k$ 个子数组是否够用？ 使用<strong>贪心策略</strong>：从左往右累加元素，只要当前和超过了 <code>mid</code>，就必须在这里“切一刀”，开启一个新的子数组。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k, <span class="type">int</span> m)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> cnt&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (acc + nums[i] &lt;= m) &#123;</span><br><span class="line">                acc += nums[i];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                acc = nums[i];</span><br><span class="line">                cnt++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt &lt;= k;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">splitArray</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> l = *<span class="built_in">max_element</span>(nums.<span class="built_in">begin</span>(), nums.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> r = <span class="built_in">accumulate</span>(nums.<span class="built_in">begin</span>(), nums.<span class="built_in">end</span>(), <span class="number">0</span>);</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> m = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(nums, k, m)) &#123;</span><br><span class="line">                <span class="comment">// 满足条件</span></span><br><span class="line">                r = m - <span class="number">1</span>;</span><br><span class="line">                ans = m;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                l = m + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>简单的题目能够一眼看出“查找”任务，读者也就可以联想到使用二分法；中等级别或困难级别的题目往往背景复杂，无法马上看出是“查找”任务，需要读者进一步加工并对题目进行转换，利用题目中的已知信息，构建查找的目标，以及目标所在的范围。此外，当题目的数据规模超过1e7时，有较大的可能是二分法类型的题目，这也是一个识别二分法的小技巧。</p><p>关注查找范围内的中间元素，挖掘背后的规律，往往<strong>中间元素和题目的目标值、左右相邻元素及左右边界元素等存在一定的关联</strong>，根据这些关联可以将查找范围缩小一半。</p><p>具体的实现方法包括原始的二分查找及二分查找的变种，二者的实现难度不大，唯一需要注意的是二分查找的变种的边界问题，当更新左边界l=mid时，需要修改循环的退出条件为l+1==h or l==h。</p><p><a href="https://leetcode.cn/problems/magnetic-force-between-two-balls/">两球之间的磁力</a></p><p>在代号为 C-137 的地球上，Rick 发现如果他将两个球放在他新发明的篮子里，它们之间会形成特殊形式的磁力。Rick 有 <code>n</code> 个空的篮子，第 <code>i</code> 个篮子的位置在 <code>position[i]</code> ，Morty 想把 <code>m</code> 个球放到这些篮子里，使得任意两球间 <strong>最小磁力</strong> 最大。</p><p>已知两个球如果分别位于 <code>x</code> 和 <code>y</code> ，那么它们之间的磁力为 <code>|x - y|</code> 。</p><p>给你一个整数数组 <code>position</code> 和一个整数 <code>m</code> ，请你返回最大化的最小磁力。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; pos, <span class="type">int</span> r, <span class="type">int</span> m)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 以r为最小磁力，能放多少个球</span></span><br><span class="line">        <span class="type">int</span> res&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="type">int</span> prevpose = pos[<span class="number">0</span>];</span><br><span class="line">        <span class="comment">// 磁力越大，能放的球越少</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; pos.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="type">int</span> p = pos[i];</span><br><span class="line">            <span class="keyword">if</span> (p - prevpose &gt;= r) &#123;</span><br><span class="line">                res++;</span><br><span class="line">                prevpose = p;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res &gt;= m;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxDistance</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; position, <span class="type">int</span> m)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">sort</span>(position.<span class="built_in">begin</span>(), position.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> l = <span class="number">1</span>, r = position.<span class="built_in">back</span>() - position.<span class="built_in">front</span>();</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(position, mid, m)) &#123;</span><br><span class="line">                ans = mid;</span><br><span class="line">                l = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                r = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>最长递增子序列</strong></p><p>给你一个整数数组 <code>nums</code> ，找到其中最长严格递增子序列的长度。</p><p><strong>子序列</strong> 是由数组派生而来的序列，删除（或不删除）数组中的元素而不改变其余元素的顺序。例如，<code>[3,6,2,7]</code> 是数组 <code>[0,3,1,6,2,2,7]</code> 的子序列。</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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;algorithm&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lengthOfLIS</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (nums.<span class="built_in">empty</span>()) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// tails[i] 存储长度为 i+1 的子序列的最小末尾</span></span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; tails;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> x : nums) &#123;</span><br><span class="line">            <span class="comment">// 使用二分查找在 tails 中找到第一个 &gt;= x 的位置</span></span><br><span class="line">            <span class="keyword">auto</span> it = <span class="built_in">lower_bound</span>(tails.<span class="built_in">begin</span>(), tails.<span class="built_in">end</span>(), x);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (it == tails.<span class="built_in">end</span>()) &#123;</span><br><span class="line">                <span class="comment">// 如果 x 比所有尾数都大，说明可以延长 LIS</span></span><br><span class="line">                tails.<span class="built_in">push_back</span>(x);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 如果找到了，就用 x 更新那个位置，减小该长度子序列的末尾值</span></span><br><span class="line">                *it = x;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> tails.<span class="built_in">size</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="位运算-1"><a href="#位运算-1" class="headerlink" title="位运算"></a>位运算</h1><p>计算机中的数据都是以二进制的形式存储的，二进制的运算都是按位来进行的。以整数为例，位运算就是直接对整数在内存中的二进制位进行操作，效率比算术运算要高。</p><p>例如，求一个数的2倍的值，使用位运算比算术运算要快很多，因为算术运算的乘法指令所用的指令周期（指令周期是指CPU从内存取出一条指令并执行这条指令的时间总和）比位运算的移位指令所用的指令周期长</p><p>位运算包括取反、按位或、按位异或、按位与、移位等操作。常见的位运算符如下。</p><ul><li>取反（~）：按位取反，1变0，0变1。</li><li>按位或（|）：操作位中只要有1，则结果为1；否则结果为0。</li><li>按位异或（^）：操作位中只要有两位相反（一个为1，一个为0），则结果为1；否则结果为0。</li><li>按位与（&amp;）：操作位中只要有两位全部为1，则结果为1；否则结果为0。</li><li>移位（&lt;&lt;或&gt;&gt;）：移位分为算术移位和逻辑移位；根据移位方向又分为左移运算和右移运算。</li></ul><p>给定一个正整数 <code>n</code>，编写一个函数，获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数（也被称为<a href="https://baike.baidu.com/item/汉明重量">汉明重量</a>）。</p><p>不去检测整数的每一位，而是依次将最低位且值为1的比特位翻转为0，并增加计数器。当执行结果使整数为0时，该整数不再包含任何为1的比特，返回计数器的值。此时的关键问题是如何执行“翻转最低有效比特为1的比特为0”，此处可以使用n &amp;(n-1)的操作。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">hammingWeight</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (n &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            n = n &amp; (n - <span class="number">1</span>);</span><br><span class="line">            res++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>两整数之和</strong></p><p>给你两个整数 <code>a</code> 和 <code>b</code> ，<strong>不使用</strong> 运算符 <code>+</code> 和 <code>-</code> ，计算并返回两整数之和。</p><p>有符号整数通常用补码来表示和存储，补码具有如下特征：</p><p>正整数的补码与原码相同；负整数的补码为其原码除符号位外的所有位取反后加 1。可以将减法运算转化为补码的加法运算来实现。符号位与数值位可以一起参与运算.</p><p><strong>逻辑右移 (Logical Right Shift)</strong></p><ul><li><strong>规则</strong>：不管三七二十一，左边统一<strong>补 0</strong>。</li><li><strong>适用</strong>：无符号数（<code>unsigned</code>）。</li><li><strong>后果</strong>：如果你把一个负数进行逻辑右移，符号位的 <code>1</code> 会被移走，左边补 <code>0</code>，这个数会瞬间从负数变成一个巨大的正数。</li></ul><p><strong>算术右移 (Arithmetic Right Shift)</strong></p><ul><li><strong>规则</strong>：左边<strong>补符号位</strong>（原来是 0 就补 0，原来是 1 就补 1）。</li><li><strong>适用</strong>：有符号数（<code>int</code>）。</li><li><strong>意义</strong>：为了维持数学上的<strong>“除以 2”</strong>。如果一个负数除以 2 之后变成了正数，那数学逻辑就崩了，所以必须补 1 来保持它的负号。</li></ul><p><strong>术左移和逻辑左移是同一条指令。</strong></p><ul><li><strong>操作</strong>：右边统一<strong>补 0</strong>。</li></ul><p>在 C++20 之前，<code>int</code> 的右移行为是由“编译器实现决定”的（Implementation-defined），虽然几乎所有现代编译器都默认使用算术右移，但理论上存在不确定性。<strong>C++20 正式将其统一为算术右移。</strong></p><p>在 Java 语言中，为了避免 C++ 这种“隐式”的规则，专门设计了两个操作符：</p><ul><li><strong><code>&gt;&gt;</code></strong>：算术右移（保留符号）。</li><li><strong><code>&gt;&gt;&gt;</code></strong>：无符号右移（逻辑右移，通通补 0）。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getSum</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (b != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">unsigned</span> <span class="type">int</span> carry = (<span class="type">unsigned</span> <span class="type">int</span>)(a &amp; b) &lt;&lt; <span class="number">1</span>;</span><br><span class="line">            a = a ^ b;</span><br><span class="line">            b = carry;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> a;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>整数替换</strong></p><p>给定一个正整数 <code>n</code> ，你可以做如下操作：</p><ol><li>如果 <code>n</code> 是偶数，则用 <code>n / 2</code>替换 <code>n</code> 。</li><li>如果 <code>n</code> 是奇数，则可以用 <code>n + 1</code>或<code>n - 1</code>替换 <code>n</code> 。</li></ol><p>返回 <code>n</code> 变为 <code>1</code> 所需的 <em>最小替换次数</em> 。</p><p>当 n 为奇数时，我们可以选择将 n 增加 1 或减少 1。由于这两种方法都会将 n 变为偶数，那么下一步一定是除以 2，因此这里我们可以看成使用两次操作，将 n 变为 (n+1)/2和(n-1)/2.</p><p><img data-src="https://img.proanimer.com/imgs/image-20260210124408364.png" alt="image-20260210124408364"></p><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><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">search</span><span class="params">(unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&amp; memo, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(n)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[n];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (n &amp; <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// n</span></span><br><span class="line">            <span class="comment">// n-1 n+1</span></span><br><span class="line">            <span class="comment">// (n-1)/2 == n//2</span></span><br><span class="line">            <span class="comment">// (n+1)/2 == n//2+1</span></span><br><span class="line">            <span class="type">int</span> l1 = <span class="built_in">search</span>(memo, n / <span class="number">2</span>);</span><br><span class="line">            <span class="type">int</span> l2 = <span class="built_in">search</span>(memo, n / <span class="number">2</span> + <span class="number">1</span>);</span><br><span class="line">            memo[n] = <span class="built_in">min</span>(l1, l2) + <span class="number">2</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">int</span> l = <span class="built_in">search</span>(memo, n / <span class="number">2</span>);</span><br><span class="line">            memo[n] = l + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> memo[n];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">integerReplacement</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt; memo;</span><br><span class="line">        memo[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">search</span>(memo, n);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>利用二进制特性的最优解。核心思想是：<strong>奇数时，通过 $+1$ 或 $-1$ 尽可能让低位产生更多的 $0$。</strong></p><p>算法逻辑：</p><ul><li><strong>若 $n$ 是偶数</strong>：直接右移（$n = n &gt;&gt; 1$）。</li><li><strong>若 $n$ 是奇数</strong>：<ul><li>观察最后两位：<ul><li>如果是 <code>...01</code>：减 1 效果更好（变为 <code>...00</code>）。</li><li>如果是 <code>...11</code>：加 1 效果更好（进位变为 <code>...00</code>）。</li></ul></li><li><strong>特例处理</strong>：当 $n = 3$ 时，二进制虽是 <code>11</code>，但 $3 \to 2 \to 1$（2步）优于 $3 \to 4 \to 2 \to 1$（3步），所以 $3$ 选择减 1。</li></ul></li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">integerReplacement</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 贪心</span></span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (n != <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (n &amp; <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="comment">// 奇数</span></span><br><span class="line">                <span class="comment">// 01</span></span><br><span class="line">                <span class="comment">// 11</span></span><br><span class="line">                <span class="keyword">if</span> (n == <span class="number">3</span>) &#123;</span><br><span class="line">                    <span class="comment">// 特殊情况</span></span><br><span class="line">                    res += <span class="number">2</span>;</span><br><span class="line">                    n = <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((n &amp; <span class="number">3</span>) == <span class="number">3</span>) &#123;</span><br><span class="line">                    res += <span class="number">2</span>;</span><br><span class="line">                    n = n / <span class="number">2</span> + <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((n &amp; <span class="number">3</span>) == <span class="number">1</span>) &#123;</span><br><span class="line">                    res += <span class="number">2</span>;</span><br><span class="line">                    n /= <span class="number">2</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 偶数</span></span><br><span class="line">                n /= <span class="number">2</span>;</span><br><span class="line">                res++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>只出现一次的数字</strong></p><p>给你一个 <strong>非空</strong> 整数数组 <code>nums</code> ，除了某个元素只出现一次以外，其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题，且该算法只使用常量额外空间。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">singleNumber</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; n:nums) &#123;</span><br><span class="line">            res ^= n;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>只出现一次的数字II</strong></p><p>给你一个整数数组 <code>nums</code> ，除某个元素仅出现 <strong>一次</strong> 外，其余每个元素都恰出现 <strong>三次 。</strong>请你找出并返回那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。</p><p>简单异或法无法筛选出唯一的单一元素，因为异或法无法将3个元素消除，但考虑到在元素的二进制形式中，对于出现3次的元素，它的二进制形式中的每一位都是3的倍数，统计所有数字的二进制形式中1出现的次数，并对3求余，如果结果不为0，则说明出现1次的数字在该二进制位上为1</p><p>逐位计数法（通用且稳健），这种方法遍历 32 个比特位，统计每一位上 $1$ 出现的次数。如果某一位的计数不能被 3 整除，那么目标数字在该位就是 $1$。如果改为出现 $k$ 次，只需将 <code>% 3</code> 改为 <code>% k</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">singleNumber</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">32</span>; i++) &#123;</span><br><span class="line">            <span class="type">int</span> r&#123;&#125;;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; n : nums) &#123;</span><br><span class="line">                r += ((n&gt;&gt;i)&amp;<span class="number">1</span>); <span class="comment">// 第i位出现次数</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (r % <span class="number">3</span> == <span class="number">1</span>) &#123;</span><br><span class="line">                res |= (<span class="number">1</span> &lt;&lt; i);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>另外还可以利用真值表推导转换为规则并行处理二进制</p><p><strong>只出现一次的数字</strong>II</p><p>给你一个整数数组 <code>nums</code>，其中恰好有两个元素只出现一次，其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 <strong>任意顺序</strong> 返回答案。你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。</p><p>假设这两个只出现一次的数字是 $x$ 和 $y$。</p><ol><li><strong>全局异或</strong>：对数组所有元素进行异或。因为出现两次的数字都会抵消（$a \oplus a = 0$），最终结果 <code>xorSum = x ^ y</code>。</li><li><strong>寻找差异位</strong>：由于 $x \neq y$，<code>xorSum</code> 必然不为 0。这意味着 $x$ 和 $y$ 的二进制表示中至少有一位是不同的（一个是 0，一个是 1）。我们取出 <code>xorSum</code> 中最低位的 1（称为 <code>lowbit</code>）。</li><li><strong>分组异或</strong>：根据 <code>lowbit</code> 这一位是否为 1，将原数组中的所有数字分成两组：<ul><li>第一组：该位为 1 的所有数字。</li><li>第二组：该位为 0 的所有数字</li></ul></li><li><strong>结果产出</strong>：<ul><li>相同的数字必然会被分到同一组并相互抵消。</li><li>$x$ 和 $y$ 必然会被分到不同的组。</li><li>对两组分别异或，剩下的两个数就是 $x$ 和 $y$。</li></ul></li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">singleNumber</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 全员异或得到 x ^ y</span></span><br><span class="line">        <span class="comment">// 使用 long 防止 INT_MIN 取负时溢出</span></span><br><span class="line">        <span class="type">long</span> xorSum = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">            xorSum ^= num;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 获取 x ^ y 中最低位的 1 (lowbit)</span></span><br><span class="line">        <span class="comment">// 这个 1 是 x 和 y 的不同点</span></span><br><span class="line">        <span class="type">int</span> lowbit = xorSum &amp; -xorSum;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 分组异或得到两个结果</span></span><br><span class="line">        <span class="type">int</span> x = <span class="number">0</span>, y = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">            <span class="keyword">if</span> (num &amp; lowbit) &#123;</span><br><span class="line">                <span class="comment">// 第一组：该位为 1</span></span><br><span class="line">                x ^= num;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 第二组：该位为 0</span></span><br><span class="line">                y ^= num;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> &#123;x, y&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在面试过程中，如果题目中出现二进制、与2的倍数相关的问题、不能使用算术运算符等情况时，都可以考虑是否可以使用位运算解题。当然，在做题的过程中，也要善于总结规律，这样在真正的笔试、面试过程中，才能够迅速写出简洁、高效的代码。另外还有一种常见的位运算使用场景是状态压缩</p><h1 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h1><p>这类题更加强调对数据结构的设计，以达到高效实现某些操作的目的。这种题目需要我们对各种基础数据结构的特性及基本操作有着非常好的理解。</p><p><strong>最小栈</strong></p><p>设计一个支持 <code>push</code> ，<code>pop</code> ，<code>top</code> 操作，并能在常数时间内检索到最小元素的栈。</p><p>实现 <code>MinStack</code> 类:</p><ul><li><code>MinStack()</code> 初始化堆栈对象。</li><li><code>void push(int val)</code> 将元素val推入堆栈。</li><li><code>void pop()</code> 删除堆栈顶部的元素。</li><li><code>int top()</code> 获取堆栈顶部的元素。</li><li><code>int getMin()</code> 获取堆栈中的最小元素。</li></ul><p>对于栈来说，如果一个元素 a 在入栈时，栈里有其它的元素 b, c, d，那么只要 a 在栈中，b, c, d 就一定在栈中。</p><p>那么在任何时候只要栈a在栈中，栈中元素一定是a,b,c,d.那么可以在每个元素 <code>a</code> 入栈时把当前栈的最小值 <code>m</code> 存储起来。在这之后无论何时，如果栈顶元素是 <code>a</code>，就可以直接返回存储的最小值 <code>m</code>。</p><p>只需要设计一个数据结构，使得每个元素 <code>a</code> 与其相应的最小值 <code>m</code> 时刻保持一一对应。因此可以使用一个辅助栈，与元素栈同步插入与删除</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MinStack</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    stack&lt;<span class="type">int</span>&gt; stk; <span class="comment">// 存储数据 正常栈</span></span><br><span class="line">    stack&lt;<span class="type">int</span>&gt; mstack;</span><br><span class="line">    <span class="built_in">MinStack</span>() &#123; mstack.<span class="built_in">push</span>(INT_MAX); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">push</span><span class="params">(<span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        stk.<span class="built_in">push</span>(val);</span><br><span class="line">        mstack.<span class="built_in">push</span>(<span class="built_in">min</span>(mstack.<span class="built_in">top</span>(),val));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 弹出一个值时需要更新最小值</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">pop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        stk.<span class="built_in">pop</span>();</span><br><span class="line">        mstack.<span class="built_in">pop</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">top</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> val = stk.<span class="built_in">top</span>();</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getMin</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> mstack.<span class="built_in">top</span>(); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>实现前缀树</strong></p><p><strong>前缀树</strong> 是一种树形数据结构，用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景，例如自动补全和拼写检查。</p><p>一棵有根树，其每个节点包含以下字段：</p><p><strong>指向子节点的指针数组 children</strong>。对于本题而言，数组长度为 26，即小写英文字母的数量。此时 children[0] 对应小写字母 a，children[1] 对应小写字母 b，…，children[25] 对应小写字母 z。<br>布尔字段 isEnd，表示该节点是否为字符串的结尾</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Trie</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        vector&lt;Node*&gt; children&#123;<span class="number">26</span>&#125;;</span><br><span class="line">        <span class="type">bool</span> isEnd&#123;<span class="literal">false</span>&#125;;</span><br><span class="line">    &#125;;</span><br><span class="line">    Node* root&#123;&#125;;</span><br><span class="line">    <span class="built_in">Trie</span>() &#123; root = <span class="keyword">new</span> <span class="built_in">Node</span>(); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">insert</span><span class="params">(string word)</span> </span>&#123;</span><br><span class="line">        Node* cur = root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : word) &#123;</span><br><span class="line">            <span class="type">int</span> idx = ch - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            Node* node = cur-&gt;children[idx];</span><br><span class="line">            <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果不存在</span></span><br><span class="line">                node = <span class="keyword">new</span> Node;</span><br><span class="line">                cur-&gt;children[idx] = node;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = node;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 当前字符结束 代表有这个字符串</span></span><br><span class="line">        cur-&gt;isEnd = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">search</span><span class="params">(string word)</span> </span>&#123;</span><br><span class="line">        Node* cur = root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : word) &#123;</span><br><span class="line">            <span class="type">int</span> idx = ch - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            Node* node = cur-&gt;children[idx];</span><br><span class="line">            <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                <span class="comment">// 不存在</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = node;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (cur-&gt;isEnd == <span class="literal">false</span>) &#123;</span><br><span class="line">            <span class="comment">// 当前字符串仍然有后续字符不存在</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">startsWith</span><span class="params">(string prefix)</span> </span>&#123;</span><br><span class="line">        Node* cur = root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : prefix) &#123;</span><br><span class="line">            <span class="type">int</span> idx = ch - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            Node* node = cur-&gt;children[idx];</span><br><span class="line">            <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                <span class="comment">// 不存在</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = node;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>LRU缓存</strong></p><p>请你设计并实现一个满足 <a href="https://baike.baidu.com/item/LRU">LRU (最近最少使用) 缓存</a> 约束的数据结构。</p><p>实现 <code>LRUCache</code> 类：</p><ul><li><code>LRUCache(int capacity)</code> 以 <strong>正整数</strong> 作为容量 <code>capacity</code> 初始化 LRU 缓存</li><li><code>int get(int key)</code> 如果关键字 <code>key</code> 存在于缓存中，则返回关键字的值，否则返回 <code>-1</code> 。</li><li><code>void put(int key, int value)</code> 如果关键字 <code>key</code> 已经存在，则变更其数据值 <code>value</code> ；如果不存在，则向缓存中插入该组 <code>key-value</code> 。如果插入操作导致关键字数量超过 <code>capacity</code> ，则应该 <strong>逐出</strong> 最久未使用的关键字。</li></ul><p>函数 <code>get</code> 和 <code>put</code> 必须以 <code>O(1)</code> 的平均时间复杂度运行。</p><p>LRU 缓存机制可以通过哈希表辅以双向链表实现，我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。</p><p>双向链表按照被使用的顺序存储了这些键值对，靠近头部的键值对是最近使用的，而靠近尾部的键值对是最久未使用的。</p><p>哈希表即为普通的哈希映射（HashMap），通过缓存数据的键映射到其在双向链表中的位置。</p><blockquote><p>在双向链表的实现中，使用一个伪头部（dummy head）和伪尾部（dummy tail）标记界限，这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。</p></blockquote><p>为什么使用双向链表而不是单向链表？其原因在于，如果想在常数时间内将链表中间的节点移动到尾部，需要能够在O(1)时间内获得当前节点的前驱节点。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 查询 unordered_map</span></span><br><span class="line">    <span class="comment">// 增加 双向链表 增/改 将节点放在首位</span></span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, list&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt;::iterator&gt; umap;</span><br><span class="line">    <span class="comment">// class LinkedNode&#123;</span></span><br><span class="line">    <span class="comment">//     public:</span></span><br><span class="line">    <span class="comment">//         int key&#123;&#125;;</span></span><br><span class="line">    <span class="comment">//         int val&#123;&#125;;</span></span><br><span class="line">    <span class="comment">//         LinkedNode* next_node&#123;&#125;;</span></span><br><span class="line">    <span class="comment">//         LinedNode* prev_node&#123;&#125;;</span></span><br><span class="line">    <span class="comment">// &#125;;</span></span><br><span class="line">    <span class="comment">// LinkedNode * root;</span></span><br><span class="line">    <span class="comment">// unordered_map&lt;int,LinkedNode*&gt; umap</span></span><br><span class="line">    list&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; vals;</span><br><span class="line">    <span class="type">int</span> _capacity&#123;&#125;;</span><br><span class="line">    <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">    <span class="built_in">LRUCache</span>(<span class="type">int</span> capacity) : _capacity(capacity) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">get</span><span class="params">(<span class="type">int</span> key)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获取值</span></span><br><span class="line">        <span class="keyword">if</span> (umap.<span class="built_in">count</span>(key)) &#123;</span><br><span class="line">            <span class="comment">// 返回值 并更新节点在链表中位置</span></span><br><span class="line">            <span class="keyword">auto</span> it = umap[key];</span><br><span class="line">            <span class="type">int</span> val = it-&gt;second;</span><br><span class="line">            <span class="comment">// 插入在map中值</span></span><br><span class="line">            umap.<span class="built_in">erase</span>(key);</span><br><span class="line">            vals.<span class="built_in">erase</span>(it); <span class="comment">// 在双向链表中删除</span></span><br><span class="line">            <span class="comment">// 插入值</span></span><br><span class="line">            vals.<span class="built_in">push_front</span>(&#123;key, val&#125;);</span><br><span class="line">            umap[key] = vals.<span class="built_in">begin</span>(); <span class="comment">// 更新</span></span><br><span class="line">            <span class="keyword">return</span> val;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 不存在</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (umap.<span class="built_in">count</span>(key)) &#123;</span><br><span class="line">            <span class="comment">// 如果存在 更新位置</span></span><br><span class="line">            <span class="keyword">auto</span> it = umap[key];</span><br><span class="line">            vals.<span class="built_in">erase</span>(it);</span><br><span class="line">            vals.<span class="built_in">push_front</span>(&#123;key, value&#125;);</span><br><span class="line">            umap.<span class="built_in">erase</span>(key);</span><br><span class="line">            umap[key] = vals.<span class="built_in">begin</span>();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 不存在  插入值并更新位置 判断容量</span></span><br><span class="line">            vals.<span class="built_in">push_front</span>(&#123;key, value&#125;);</span><br><span class="line">            umap[key] = vals.<span class="built_in">begin</span>();</span><br><span class="line">            cnt++;</span><br><span class="line">            <span class="comment">// 判断容量</span></span><br><span class="line">            <span class="keyword">if</span> (cnt &gt; _capacity) &#123;</span><br><span class="line">                <span class="comment">// 删除链表最后一个元素</span></span><br><span class="line">                <span class="keyword">auto</span> it = vals.<span class="built_in">rbegin</span>();</span><br><span class="line">                umap.<span class="built_in">erase</span>(it-&gt;first);</span><br><span class="line">                vals.<span class="built_in">pop_back</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>实际的算法历史上，出现了很多性能更加优秀的变种，比如下面这两种。●　LRU-K算法，用于删除第k个最近使用的数据。●　ARC算法，维护了最近被删除数据的历史，特别适合用于需要连续扫描的情况。而在著名的Redis中同样实现了两个LRU算法的变种。</p><p>●　volatile-LRU：从已设置过期时间的数据集中挑选最近最少使用的数据来淘汰。</p><p>●　allkeys-LRU：从所有数据集中挑选最近最少使用的数据来淘汰。在Redis中LRU变种的具体实现细节</p><p><strong>LFU缓存</strong></p><p>请你为 <a href="https://baike.baidu.com/item/缓存算法">最不经常使用（LFU）</a>缓存算法设计并实现数据结构。</p><p>实现 <code>LFUCache</code> 类：</p><ul><li><code>LFUCache(int capacity)</code> - 用数据结构的容量 <code>capacity</code> 初始化对象</li><li><code>int get(int key)</code> - 如果键 <code>key</code> 存在于缓存中，则获取键的值，否则返回 <code>-1</code> 。</li><li><code>void put(int key, int value)</code> - 如果键 <code>key</code> 已存在，则变更其值；如果键不存在，请插入键值对。当缓存达到其容量 <code>capacity</code> 时，则应该在插入新项之前，移除最不经常使用的项。在此问题中，当存在平局（即两个或更多个键具有相同使用频率）时，应该去除 <strong>最久未使用</strong> 的键。</li></ul><p>为了确定最不常使用的键，可以为缓存中的每个键维护一个 <strong>使用计数器</strong> 。使用计数最小的键是最久未使用的键。</p><p>当一个键首次插入到缓存中时，它的使用计数器被设置为 <code>1</code> (由于 put 操作)。对缓存中的键执行 <code>get</code> 或 <code>put</code> 操作，使用计数器的值将会递增。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LFUCache</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="type">int</span> key&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> val&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> freq&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="built_in">Node</span>() &#123;</span><br><span class="line"></span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="built_in">Node</span>(<span class="type">int</span> k, <span class="type">int</span> v, <span class="type">int</span> f) : <span class="built_in">key</span>(k), <span class="built_in">val</span>(v), <span class="built_in">freq</span>(f) &#123;&#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, list&lt;Node&gt;::iterator&gt; nodeMap; <span class="comment">// 值-&gt;节点在list中位置</span></span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, list&lt;Node&gt;&gt; freqMap;           <span class="comment">// 频率-&gt;相同频率节点</span></span><br><span class="line">    <span class="type">int</span> min_freq&#123;INT_MAX&#125;;</span><br><span class="line">    <span class="type">int</span> _capacity&#123;&#125;;</span><br><span class="line">    <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">    <span class="built_in">LFUCache</span>(<span class="type">int</span> capacity) : _capacity(capacity) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">get</span><span class="params">(<span class="type">int</span> key)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!nodeMap.<span class="built_in">count</span>(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果存在 频率+1,更新双向链表中位置/时间</span></span><br><span class="line">        <span class="comment">// 找到节点</span></span><br><span class="line">        <span class="type">int</span> val = <span class="built_in">touch</span>(key);</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">touch</span><span class="params">(<span class="type">int</span> key)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">auto</span> it = nodeMap[key];</span><br><span class="line">        <span class="type">int</span> val = it-&gt;val;</span><br><span class="line">        <span class="type">int</span> freq = it-&gt;freq;</span><br><span class="line">        <span class="comment">// 移除频率中该节点值</span></span><br><span class="line">        freqMap[freq].<span class="built_in">erase</span>(it);</span><br><span class="line">        <span class="keyword">if</span>(freqMap[freq].<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            freqMap.<span class="built_in">erase</span>(freq);</span><br><span class="line">            <span class="keyword">if</span>(freq == min_freq) &#123;</span><br><span class="line">                min_freq++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ++freq;</span><br><span class="line">        freqMap[freq].<span class="built_in">push_front</span>(&#123;key, val, freq&#125;);</span><br><span class="line">        nodeMap[key] = freqMap[freq].<span class="built_in">begin</span>();</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 如果存在 更新频率与位置</span></span><br><span class="line">        <span class="keyword">if</span> (nodeMap.<span class="built_in">count</span>(key)) &#123;</span><br><span class="line">            <span class="keyword">auto</span> it = nodeMap[key];</span><br><span class="line">            it-&gt;val = value;</span><br><span class="line">            <span class="built_in">touch</span>(key);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 不存在</span></span><br><span class="line">        <span class="keyword">if</span> (cnt == _capacity) &#123;</span><br><span class="line">            <span class="comment">// 淘汰频率最低的值</span></span><br><span class="line">            <span class="keyword">auto</span>&amp; nodes = freqMap[min_freq];</span><br><span class="line">            <span class="comment">// 删除链表中最后一个节点</span></span><br><span class="line">            <span class="keyword">auto</span> it = nodes.<span class="built_in">rbegin</span>();</span><br><span class="line">            nodeMap.<span class="built_in">erase</span>(it-&gt;key);</span><br><span class="line">            nodes.<span class="built_in">pop_back</span>();</span><br><span class="line">            <span class="keyword">if</span> (nodes.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">                freqMap.<span class="built_in">erase</span>(min_freq);</span><br><span class="line">            &#125;</span><br><span class="line">            cnt--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 添加节点</span></span><br><span class="line">        <span class="comment">// 移除频率中该节点值</span></span><br><span class="line">        freqMap[<span class="number">1</span>].<span class="built_in">push_front</span>(&#123;key, value, <span class="number">1</span>&#125;);</span><br><span class="line">        nodeMap[key] = freqMap[<span class="number">1</span>].<span class="built_in">begin</span>();</span><br><span class="line">        min_freq = <span class="number">1</span>;</span><br><span class="line">        cnt++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>设计跳表</strong></p><p><strong>跳表</strong> 是在 <code>O(log(n))</code> 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树，其功能与性能相当，并且跳表的代码长度相较下更短，其设计思想与链表相似。</p><p>跳表是一种随机化的数据结构，可以被看做二叉树的一个变种，它在性能上和红黑树、AVL 树不相上下，但是跳表的原理非常简单，目前在 Redis 和 LevelDB 中都有用到。跳表的期望空间复杂度为 O(n)，跳表的查询，插入和删除操作的期望时间复杂度均为 O(logn)。</p><p>跳表实际为一种多层的有序链表，跳表的每一层都为一个有序链表，且满足每个位于第 i 层的节点有 p 的概率出现在第 i+1 层，其中 p 为常数。</p><p>查找时，从当前最高层开始开始，如果当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点，然后移动至下一层进行查找，重复这个过程直至到达第一层。此时，若下一个节点是目标节点，则成功查找；反之，则元素不存在。由于从高层往低层开始查找，由于低层出现的元素可能不会出现在高层，因此跳表在进行查找的过程中会跳过一些元素，相比于有序链表的查询，跳表的查询速度会更快。</p><p>添加时，从跳表的当前的最大层数 level 层开始查找，在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点，然后移动至下一层进行查找，重复这个过程直至到达第 1 层。设新加入的节点为 newNode，我们需要计算出此次节点插入的层数 lv，如果 level 小于 lv，则同时需要更新 level。用数组 update 保存每一层查找的最后一个节点，第 i 层最后的节点为 update[i]。我们将 newNode 的后续节点指向 update[i] 的下一个节点，同时更新 update[i] 的后续节点为 newNode。 lv随机生成，从第一层开始，如果概率小于某个值，则再加一层。</p><p>删除时，首先我们需要查找当前元素是否存在跳表中。从跳表的当前的最大层数 level 层开始查找，在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点，然后移动至下一层进行查找，重复这个过程直至到达第 1 层。如果第 1 层的下一个节点不等于 num 时，则表示当前元素不存在直接返回。我们用数组 update 保存每一层查找的最后一个节点，第 i 层最后的节点为 update[i]。此时第 i 层的下一个节点的值为 num，则我们需要将其从跳表中将其删除。由于第 i 层的以 p 的概率出现在第 i+1 层，因此我们应当从第 1 层开始往上进行更新，将 num 从 update[i] 的下一跳中删除，同时更新 update[i] 的后续节点，直到当前层的链表中没有出现 num 的节点为止。最后我们还需要更新跳表中当前的最大层数 level。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Skiplist</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">const</span> <span class="type">static</span> <span class="type">int</span> MAX_LEVEL = <span class="number">32</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">static</span> <span class="type">int</span> factor = <span class="number">.25</span>;</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">SkipListNode</span> &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="type">int</span> val&#123;&#125;;</span><br><span class="line">        vector&lt;SkipListNode*&gt; next;</span><br><span class="line">        <span class="built_in">SkipListNode</span>(<span class="type">int</span> t_val, <span class="type">int</span> max_level = MAX_LEVEL)</span><br><span class="line">            : <span class="built_in">val</span>(t_val), <span class="built_in">next</span>(max_level, <span class="literal">nullptr</span>) &#123;&#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    SkipListNode* skipNode;</span><br><span class="line">    <span class="type">int</span> level&#123;&#125;;</span><br><span class="line">    <span class="built_in">Skiplist</span>() &#123; skipNode = <span class="keyword">new</span> <span class="built_in">SkipListNode</span>(<span class="number">-1</span>); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">search</span><span class="params">(<span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 从最顶层搜索，</span></span><br><span class="line">        SkipListNode* cur = skipNode;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = level - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="comment">// 如果同层下一个节点值小于target 继续到下一个节点</span></span><br><span class="line">            <span class="comment">// 否则跳向下一层</span></span><br><span class="line">            <span class="keyword">while</span> (cur-&gt;next[i] &amp;&amp; cur-&gt;next[i]-&gt;val &lt; target) &#123;</span><br><span class="line">                cur = cur-&gt;next[i];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        cur = cur-&gt;next[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">if</span> (cur &amp;&amp; cur-&gt;val == target) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">randomLevel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> lv&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="keyword">while</span> (<span class="built_in">rand</span>() &lt; factor &amp;&amp; lv &lt; MAX_LEVEL) &#123;</span><br><span class="line">            lv++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> lv;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;SkipListNode*&gt; <span class="title">update</span><span class="params">(MAX_LEVEL, skipNode)</span></span>;</span><br><span class="line">        SkipListNode* cur = skipNode;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = level - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">while</span> (cur-&gt;next[i] &amp;&amp; cur-&gt;next[i]-&gt;val &lt; num) &#123;</span><br><span class="line">                cur = cur-&gt;next[i];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 记录每层要添加的位置</span></span><br><span class="line">            update[i] = cur;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> lv = <span class="built_in">randomLevel</span>();</span><br><span class="line">        level = <span class="built_in">max</span>(lv, MAX_LEVEL);</span><br><span class="line">        SkipListNode* newNode = <span class="keyword">new</span> <span class="built_in">SkipListNode</span>(num, lv);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; lv; i++) &#123;</span><br><span class="line">            <span class="comment">// 到lv之前 每层都添加</span></span><br><span class="line">            newNode-&gt;next[i] = update[i]-&gt;next[i];</span><br><span class="line">            update[i]-&gt;next[i] = newNode;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">erase</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;SkipListNode*&gt; <span class="title">update</span><span class="params">(MAX_LEVEL, <span class="literal">nullptr</span>)</span></span>;</span><br><span class="line">        <span class="comment">// 记录从最高层要删除的节点之前的节点</span></span><br><span class="line">        SkipListNode* cur = skipNode;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = level - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">while</span> (cur-&gt;next[i] &amp;&amp; cur-&gt;next[i]-&gt;val &lt; num) &#123;</span><br><span class="line">                cur = cur-&gt;next[i];</span><br><span class="line">            &#125;</span><br><span class="line">            update[i] = cur;</span><br><span class="line">        &#125;</span><br><span class="line">        cur = cur-&gt;next[<span class="number">0</span>];</span><br><span class="line">        <span class="comment">// 看要删除的值是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (!cur || cur-&gt;val != num) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; level; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (update[i]-&gt;next[i] != cur) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            update[i]-&gt;next[i] = cur-&gt;next[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">delete</span> cur;</span><br><span class="line">        <span class="keyword">while</span> (level &gt; <span class="number">1</span> &amp;&amp; skipNode-&gt;next[level - <span class="number">1</span>] == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            level--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>添加与搜索单词</strong></p><p>字典树（前缀树）是一种树形数据结构，用于高效地存储和检索字符串数据集中的键。前缀树可以用 O(∣S∣) 的时间复杂度完成如下操作，其中 ∣S∣ 是插入字符串或查询前缀的长度：</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">WordDictionary</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">        <span class="type">char</span> ch&#123;&#125;;</span><br><span class="line">        vector&lt;Node*&gt; next&#123;<span class="number">26</span>, <span class="literal">nullptr</span>&#125;;</span><br><span class="line">        <span class="comment">// 是否是字符串结尾</span></span><br><span class="line">        <span class="type">bool</span> isEnd&#123;&#125;;</span><br><span class="line">    &#125;;</span><br><span class="line">    Node* root&#123;&#125;;</span><br><span class="line">    <span class="built_in">WordDictionary</span>() &#123; root = <span class="keyword">new</span> Node; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">addWord</span><span class="params">(string word)</span> </span>&#123;</span><br><span class="line">        Node* cur = root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : word) &#123;</span><br><span class="line">            Node* node = cur-&gt;next[ch - <span class="string">&#x27;a&#x27;</span>];</span><br><span class="line">            <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                node = <span class="keyword">new</span> Node;</span><br><span class="line">                node-&gt;ch = ch;</span><br><span class="line">                cur-&gt;next[ch - <span class="string">&#x27;a&#x27;</span>] = node;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = node;</span><br><span class="line">        &#125;</span><br><span class="line">        cur-&gt;isEnd = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">dfs</span><span class="params">(string&amp; word, <span class="type">int</span> idx, Node* cur)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (idx == word.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (cur-&gt;isEnd == <span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">char</span> ch = word[idx];</span><br><span class="line">        <span class="keyword">if</span> (ch == <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">            <span class="comment">// 针对下一个节点</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">26</span>; i++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (cur-&gt;next[i]) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="built_in">dfs</span>(word, idx + <span class="number">1</span>, cur-&gt;next[i])) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">int</span> index = ch - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            <span class="keyword">if</span> (cur-&gt;next[index]) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="built_in">dfs</span>(word, idx + <span class="number">1</span>, cur-&gt;next[index]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">search</span><span class="params">(string word)</span> </span>&#123;</span><br><span class="line">        Node* cur = root;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(word, <span class="number">0</span>, cur);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>二叉树的序列化与反序列化</strong></p><p>序列化是将一个数据结构或者对象转换为连续的比特位的操作，进而可以将转换后的数据存储在一个文件或者内存中，同时也可以通过网络传输到另一个计算机环境，采取相反方式重构得到原数据。</p><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Codec</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 序列为字符串</span></span><br><span class="line">    <span class="comment">// 依规定顺序和规则序列化和反序列化</span></span><br><span class="line">    <span class="comment">// DFS 前序 中序 后序</span></span><br><span class="line">    <span class="comment">// BFS 层序</span></span><br><span class="line">    <span class="comment">// Encodes a tree to a single string.</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">serializeTree</span><span class="params">(TreeNode* node, string&amp; str)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="comment">// 根节点</span></span><br><span class="line">            str += <span class="string">&quot;None,&quot;</span>;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        str += <span class="built_in">to_string</span>(node-&gt;val) + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">        <span class="built_in">serializeTree</span>(node-&gt;left, str);</span><br><span class="line">        <span class="built_in">serializeTree</span>(node-&gt;right, str);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">string <span class="title">serialize</span><span class="params">(TreeNode* root)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 规定序列化顺序</span></span><br><span class="line">        <span class="comment">// 先序</span></span><br><span class="line">        string res;</span><br><span class="line">        <span class="built_in">serializeTree</span>(root, res);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">TreeNode* <span class="title">rserialize</span><span class="params">(list&lt;string&gt;&amp; dataArray)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 根据数组值</span></span><br><span class="line">        <span class="keyword">if</span> (dataArray.<span class="built_in">front</span>() == <span class="string">&quot;None&quot;</span>) &#123;</span><br><span class="line">            <span class="comment">// 空节点</span></span><br><span class="line">            dataArray.<span class="built_in">erase</span>(dataArray.<span class="built_in">begin</span>());</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        TreeNode* node = <span class="keyword">new</span> <span class="built_in">TreeNode</span>(<span class="built_in">stoi</span>(dataArray.<span class="built_in">front</span>()));</span><br><span class="line">        dataArray.<span class="built_in">erase</span>(dataArray.<span class="built_in">begin</span>());</span><br><span class="line">        node-&gt;left = <span class="built_in">rserialize</span>(dataArray);</span><br><span class="line">        node-&gt;right = <span class="built_in">rserialize</span>(dataArray);</span><br><span class="line">        <span class="keyword">return</span> node;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Decodes your encoded data to tree.</span></span><br><span class="line">    <span class="function">TreeNode* <span class="title">deserialize</span><span class="params">(string data)</span> </span>&#123;</span><br><span class="line">        list&lt;string&gt; dataArray;</span><br><span class="line">        string str;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>&amp; ch : data) &#123;</span><br><span class="line">            <span class="keyword">if</span> (ch == <span class="string">&#x27;,&#x27;</span>) &#123;</span><br><span class="line">                dataArray.<span class="built_in">push_back</span>(str);</span><br><span class="line">                str.<span class="built_in">clear</span>();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                str.<span class="built_in">push_back</span>(ch);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!str.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            dataArray.<span class="built_in">push_back</span>(str);</span><br><span class="line">            str.<span class="built_in">clear</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 拆分为数组</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">rserialize</span>(dataArray);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以先序遍历这颗二叉树，遇到空子树的时候序列化成 None，否则继续递归序列化。那么我们如何反序列化呢？首先我们需要根据 , 把原先的序列分割开来得到先序遍历的元素列表，然后从左向右遍历这个序列：</p><p>如果当前的元素为 None，则当前为空树.否则先解析这棵树的左子树，再解析它的右子树</p><p><strong>字符的编码与解码</strong></p><p>请你设计一个算法，可以将一个 <strong>字符串列表</strong> 编码成为一个 <strong>字符串</strong>。这个编码后的字符串是可以通过网络进行高效传送的，并且可以在接收端被解码回原来的字符串列表。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Codec</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">strLen</span><span class="params">(string&amp; str)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = str.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">to_string</span>(sz) + <span class="string">&quot;#&quot;</span> + str;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Encodes a list of strings to a single string.</span></span><br><span class="line">    <span class="function">string <span class="title">encode</span><span class="params">(vector&lt;string&gt;&amp; strs)</span> </span>&#123;</span><br><span class="line">        string res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; str : strs) &#123;</span><br><span class="line">            res += <span class="built_in">strLen</span>(str);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Decodes a single string to a list of strings.</span></span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">decode</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        vector&lt;string&gt; res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="type">int</span> num&#123;&#125;;</span><br><span class="line">            <span class="keyword">while</span> (i &lt; s.<span class="built_in">size</span>() &amp;&amp; <span class="built_in">isdigit</span>(s[i])) &#123;</span><br><span class="line">                num = num * <span class="number">10</span> + (s[i] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">                i++;</span><br><span class="line">            &#125;</span><br><span class="line">            i++;</span><br><span class="line">            res.<span class="built_in">push_back</span>(s.<span class="built_in">substr</span>(i, num));</span><br><span class="line">            i = i+num<span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>循环双端队列</strong></p><p>以通过一个数组进行模拟，通过操作数组的索引构建一个虚拟的首尾相连的环。在循环队列结构中，设置一个队尾 <em>rear</em> 与队首 <em>front</em>，且大小固定</p><p>在循环队列中，当队列为空，可知 front=rear；而当所有队列空间全占满时，也有 front=rear。为了区别这两种情况，假设队列使用的数组有 capacity 个存储空间，则此时规定循环队列最多只能有capacity−1 个队列元素，当循环队列中只剩下一个空存储单元时，则表示队列已满。根据以上可知，队列判空的条件是 front=rear，而队列判满的条件是 front=(rear+1)modcapacity。</p><p>elements：一个固定大小的数组，用于保存循环队列的元素。<br>capacity：循环队列的容量，即队列中最多可以容纳的元素数量。<br>front：队列首元素对应的数组的索引。<br>rear：队列尾元素对应的索引的下一个索引。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyCircularDeque</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> _cap&#123;&#125;;</span><br><span class="line">    <span class="type">int</span>* arr;</span><br><span class="line">    <span class="type">int</span> start&#123;&#125;;</span><br><span class="line">    <span class="type">int</span> end&#123;&#125;; <span class="comment">// 记录最后一个位置+1</span></span><br><span class="line">    <span class="built_in">MyCircularDeque</span>(<span class="type">int</span> k) &#123;</span><br><span class="line">        _cap = k + <span class="number">1</span>;</span><br><span class="line">        arr = <span class="keyword">new</span> <span class="type">int</span>[_cap];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">insertFront</span><span class="params">(<span class="type">int</span> value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isFull</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[(start - <span class="number">1</span> + _cap) % _cap] = value;</span><br><span class="line">        start = (start - <span class="number">1</span> + _cap) % _cap;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">insertLast</span><span class="params">(<span class="type">int</span> value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isFull</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[end] = value;</span><br><span class="line">        end = (end + <span class="number">1</span>) % _cap;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">deleteFront</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isEmpty</span>())</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        start = (start + <span class="number">1</span>) % _cap;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">deleteLast</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isEmpty</span>())</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        end = (end - <span class="number">1</span> + _cap) % _cap;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getFront</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isEmpty</span>())</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">return</span> arr[start];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getRear</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">isEmpty</span>())</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">return</span> arr[(end - <span class="number">1</span> + _cap) % _cap];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> start == end; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isFull</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> ((end + <span class="number">1</span>) % _cap) == start; &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="双指针"><a href="#双指针" class="headerlink" title="双指针"></a>双指针</h1><p>双指针是一个很宽泛的概念，就像数组、链表一样，其类型有很多。比如二分法经常用到左/右端点双指针，滑动窗口会用到快/慢指针和固定间距指针，因此双指针其实是一种综合性很强的类型，类似于数组、栈等，但是这里所讲述的双指针，往往指的是某几种类型的双指针，而不是只要有两个指针就是双指针</p><p>头/尾指针是指游标同时指向数组、字符串的第一个元素和最后一个元素，典型应用是求数组元素或子串是否满足特定条件。快/慢指针是指两个指针的移动速度不同（比如有的移动步长为2，有的移动步长为1），典型的应用是判断链表是否有环。</p><p><strong>盛最多水的容器</strong></p><p>给定一个长度为 <code>n</code> 的整数数组 <code>height</code> 。有 <code>n</code> 条垂线，第 <code>i</code> 条线的两个端点是 <code>(i, 0)</code> 和 <code>(i, height[i])</code> 。找出其中的两条线，使得它们与 <code>x</code> 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxArea</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; height)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 双指针</span></span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> l = <span class="number">0</span>, r = height.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (l &lt; r) &#123;</span><br><span class="line">            <span class="type">int</span> h = <span class="built_in">min</span>(height[l], height[r]);</span><br><span class="line">            ans = <span class="built_in">max</span>(ans, (r-l) * h);</span><br><span class="line">            <span class="keyword">if</span> (height[l] &lt; height[r]) &#123;</span><br><span class="line">                l++;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                r--;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>环形链表</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">hasCycle</span><span class="params">(ListNode *head)</span> </span>&#123;</span><br><span class="line">        ListNode* slow = head,*fast = head;</span><br><span class="line">        <span class="keyword">while</span>(fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            <span class="keyword">if</span>(slow == fast) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>无重复字符的最长字串</strong></p><p>给定一个字符串 <code>s</code> ，请你找出其中不含有重复字符的 <strong>最长 子串</strong> 的长度</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lengthOfLongestSubstring</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 记录字串内重复出现字符</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">chs</span><span class="params">(<span class="number">128</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> left&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">while</span> (left&lt;i &amp;&amp; chs[s[i]]) &#123;</span><br><span class="line">                <span class="comment">// 出现过 移动左窗口</span></span><br><span class="line">                chs[s[left]] = <span class="literal">false</span>;</span><br><span class="line">                left++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 记录</span></span><br><span class="line">            chs[s[i]] = <span class="literal">true</span>;</span><br><span class="line">            ans = <span class="built_in">max</span>(ans, i - left + <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>双指针一般解决的是与字符串、数组、链表相关的问题，当题目中出现需要字符串的子串、数组的几个子元素、链表的多个节点时，都可以考虑一下能否用双指针的方法来解决。一旦确定可以使用双指针来解答问题，要注意是否可以通过边界条件提前退出检测、是否可以利用其他结构体（比如字典等）来优化解法</p><p>双指针问题首先要考虑使用的双指针类型是选择从两边开始的头/尾指针，还是选择从一边开始的快/慢指针。针对快/慢指针，在用于滑动窗口问题时，还可以考虑通过指针控制窗口的大小来优化算法</p><h1 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h1><p>动态规划和其他算法思想如递归、回溯、分治和贪心等方法都有一定的联系。其背后的基本思想是枚举，虽然看起来简单，但如何涵盖所有的可能，并尽量减少重叠子问题的计算是一个难点。</p><p>解动态规划类问题，分析过程是有章可循的，<strong>通过对阶段、子问题和状态的拆解基本可以得到解决问题的框架</strong>。具体的求解过程一般可以<strong>通过推导状态转移方程或填状态转移表这两种方法</strong>来实现。</p><p>解决动态规划问题的核心在于找到状态转移方程和处理边界条件。这两者中更为困难的当然是状态转移方程了，看出了状态转移方程，解题就是水到渠成的事情了。对于某一道动态规划题目来说，状态转移方程可能不止一种，不同的状态转移方程对应不同的解法，而不同的转移方程的性能差别可能是巨大的</p><p><strong>鸡蛋掉落问题</strong></p><p>给你 <code>k</code> 枚相同的鸡蛋，并可以使用一栋从第 <code>1</code> 层到第 <code>n</code> 层共有 <code>n</code> 层楼的建筑。</p><p>已知存在楼层 <code>f</code> ，满足 <code>0 &lt;= f &lt;= n</code> ，任何从 <strong>高于</strong> <code>f</code> 的楼层落下的鸡蛋都会碎，从 <code>f</code> 楼层或比它低的楼层落下的鸡蛋都不会破。每次操作，你可以取一枚没有碎的鸡蛋并把它从任一楼层 <code>x</code> 扔下（满足 <code>1 &lt;= x &lt;= n</code>）。如果鸡蛋碎了，你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎，则可以在之后的操作中 <strong>重复使用</strong> 这枚鸡蛋。请你计算并返回要确定 <code>f</code> <strong>确切的值</strong> 的 <strong>最小操作次数</strong> 是多少？</p><p>可以<strong>转换思路</strong>，换一个视角来看：<strong>如果我们有 $k$ 个鸡蛋，允许扔 $m$ 次，我们最高能测出多少层楼？</strong></p><p>设 $dp[m][k]$ 为：当有 <strong>$k$ 个鸡蛋</strong>，可以<strong>扔 $m$ 次</strong>时，能确定的<strong>最大楼层数</strong>。</p><p>当我们从某一层扔下一枚鸡蛋时，只有两种结果：</p><ol><li><strong>鸡蛋碎了</strong>：<ul><li>我们损失了一个鸡蛋（剩下 $k-1$ 个）。</li><li>我们损失了一次机会（剩下 $m-1$ 次）。</li><li>我们能向下确定的楼层数是 $dp[m-1][k-1]$。</li></ul></li><li><strong>鸡蛋没碎</strong>：<ul><li>鸡蛋数量不变（剩下 $k$ 个）。</li><li>我们损失了一次机会（剩下 $m-1$ 次）。</li><li>我们能向上确定的楼层数是 $dp[m-1][k]$。</li></ul></li></ol><p>再加上<strong>当前扔鸡蛋的那一层</strong>（1 层），总共能确定的楼层数为：</p><script type="math/tex; mode=display">dp[m][k] = dp[m-1][k-1] + dp[m-1][k] + 1</script><ol><li>初始化一个二维数组（或者优化为一维）。</li><li>不断增加投掷次数 $m$，直到 $dp[m][k] \ge n$。</li><li>此时的 $m$ 就是我们要找的<strong>最小操作次数</strong>。</li></ol><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="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">superEggDrop</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(k + <span class="number">1</span>));</span><br><span class="line">        <span class="comment">// 1层楼</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= k; i++) &#123;</span><br><span class="line">            dp[<span class="number">1</span>][i] = <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= k; j++) &#123;</span><br><span class="line">                dp[i][j] = dp[i - <span class="number">1</span>][j] + dp[i - <span class="number">1</span>][j - <span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (dp[i][k] &gt;= n) &#123;</span><br><span class="line">                ans = i;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>打家劫舍系列</strong></p><p><strong>如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警</strong>。给定一个代表每个房屋存放金额的非负整数数组，计算你 <strong>不触动警报装置的情况下</strong> ，一夜之内能够偷窃到的最高金额。</p><p>如果房屋数量大于两间，应该如何计算能够偷窃到的最高总金额呢？对于第 k (k&gt;2) 间房屋，有两个选项：</p><ol><li><p>偷窃第 k 间房屋，那么就不能偷窃第 k−1 间房屋，偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。</p></li><li><p>不偷窃第 k 间房屋，偷窃总金额为前 k−1 间房屋的最高总金额。</p></li></ol><p>在两个选项中选择偷窃总金额较大的选项，该选项对应的偷窃总金额即为前 k 间房屋能偷窃到的最高总金额。</p><p>从状态转移方程可以知道状态f(n)只依赖状态f(n-1)和状态f(n-2)，因此，额外的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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">rob</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span>(sz == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(sz)</span></span>;</span><br><span class="line">        dp[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line">        dp[<span class="number">1</span>] = <span class="built_in">max</span>(nums[<span class="number">0</span>],nums[<span class="number">1</span>]);</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">2</span>;i&lt;sz;i++) &#123;</span><br><span class="line">            dp[i] = <span class="built_in">max</span>(dp[i<span class="number">-2</span>]+nums[i],dp[i<span class="number">-1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz<span class="number">-1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">rob</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (sz == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> prev = <span class="number">0</span>, cur = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">int</span> tmp = cur;</span><br><span class="line">            cur = <span class="built_in">max</span>(cur, nums[i] + prev);</span><br><span class="line">            prev = tmp;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cur;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这个地方所有的房屋都 <strong>围成一圈</strong> ，这意味着第一个房屋和最后一个房屋是紧挨着的。同时，相邻的房屋装有相互连通的防盗系统，<strong>如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警</strong> 。给定一个代表每个房屋存放金额的非负整数数组，计算你 <strong>在不触动警报装置的情况下</strong> ，今晚能够偷窃到的最高金额。</p><p>首尾的房屋是相连的，也就是说：如果偷了开头的房屋，那么结尾的房屋不能偷；如果偷了结尾的房屋，那么开头的房屋不能偷。范围[0,n-1)的解和范围[1,n-1]的解中的较大值即为解，这里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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">robRange</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> start, <span class="type">int</span> end)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> first = nums[start], second = <span class="built_in">max</span>(nums[start], nums[start + <span class="number">1</span>]);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = start + <span class="number">2</span>; i &lt;= end; i++) &#123;</span><br><span class="line">            <span class="type">int</span> temp = second;</span><br><span class="line">            second = <span class="built_in">max</span>(first + nums[i], second);</span><br><span class="line">            first = temp;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> second;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">rob</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> length = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (length == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[<span class="number">0</span>];</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (length == <span class="number">2</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">max</span>(nums[<span class="number">0</span>], nums[<span class="number">1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(<span class="built_in">robRange</span>(nums, <span class="number">0</span>, length - <span class="number">2</span>), <span class="built_in">robRange</span>(nums, <span class="number">1</span>, length - <span class="number">1</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>小偷又发现了一个新的可行窃的地区。这个地区只有一个入口，我们称之为 <code>root</code> 。</p><p>除了 <code>root</code> 之外，每栋房子有且只有一个“父“房子与之相连。一番侦察之后，聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 <strong>两个直接相连的房子在同一天晚上被打劫</strong> ，房屋将自动报警。</p><p>给定二叉树的 <code>root</code> 。返回 <strong>*在不触动警报的情况下</strong> ，小偷能够盗取的最高金额* 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">pair&lt;<span class="type">int</span>,<span class="type">int</span>&gt; <span class="title">dfs</span><span class="params">(TreeNode* node)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="number">0</span>,<span class="number">0</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 偷当前节点</span></span><br><span class="line">        <span class="keyword">auto</span> [l1,l2] = <span class="built_in">dfs</span>(node-&gt;left);</span><br><span class="line">        <span class="keyword">auto</span> [r1,r2] = <span class="built_in">dfs</span>(node-&gt;right);</span><br><span class="line">        <span class="comment">// 偷当前节点</span></span><br><span class="line">        <span class="type">int</span>  f1 = node-&gt;val + l2+r2;</span><br><span class="line">        <span class="comment">// 不偷当前节点</span></span><br><span class="line">        <span class="type">int</span> f2 = <span class="built_in">max</span>(l1,l2)+<span class="built_in">max</span>(r1,r2);</span><br><span class="line">        <span class="keyword">return</span> &#123;f1,f2&#125;; </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">rob</span><span class="params">(TreeNode* root)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!root) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">auto</span> [x1,x2] = <span class="built_in">dfs</span>(root);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(x1,x2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>不同路径</strong></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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">uniquePaths</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(n,<span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i =<span class="number">1</span>;i&lt;m;i++) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j =<span class="number">1</span>;j&lt;n;j++) &#123;</span><br><span class="line">                dp[j] += dp[j<span class="number">-1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[n<span class="number">-1</span>];</span><br><span class="line">        <span class="comment">// vector&lt;vector&lt;int&gt;&gt; dp(m, vector&lt;int&gt;(n));</span></span><br><span class="line">        <span class="comment">// for (int i = 0; i &lt; m; i++) &#123;</span></span><br><span class="line">        <span class="comment">//     dp[i][0] = 1;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        <span class="comment">// for (int i = 0; i &lt; n; i++) &#123;</span></span><br><span class="line">        <span class="comment">//     dp[0][i] = 1;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        <span class="comment">// for (int i = 1; i &lt; m; i++) &#123;</span></span><br><span class="line">        <span class="comment">//     for (int j = 1; j &lt; n; j++) &#123;</span></span><br><span class="line">        <span class="comment">//         dp[i][j] = dp[i - 1][j] + dp[i][j - 1];</span></span><br><span class="line">        <span class="comment">//     &#125;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        <span class="comment">// return dp[m - 1][n - 1];</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给定一个 <code>m x n</code> 的整数数组 <code>grid</code>。一个机器人初始位于 <strong>左上角</strong>（即 <code>grid[0][0]</code>）。机器人尝试移动到 <strong>右下角</strong>（即 <code>grid[m - 1][n - 1]</code>）。机器人每次只能向下或者向右移动一步。</p><p>网格中的障碍物和空位置分别用 <code>1</code> 和 <code>0</code> 来表示。机器人的移动路径中不能包含 <strong>任何</strong> 有障碍物的方格。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">uniquePathsWithObstacles</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; obstacleGrid)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 动态规划</span></span><br><span class="line">        <span class="type">int</span> m = obstacleGrid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = obstacleGrid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// dp[i][j]表示到达i,j的路径数目</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(m, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (obstacleGrid[<span class="number">0</span>][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                dp[<span class="number">0</span>][j] = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (obstacleGrid[i][<span class="number">0</span>] == <span class="number">0</span>) &#123;</span><br><span class="line">                dp[i][<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (obstacleGrid[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                dp[i][j] = dp[i - <span class="number">1</span>][j] + dp[i][j - <span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[m - <span class="number">1</span>][n - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>零钱系列</strong></p><p>给你一个整数数组 <code>coins</code> ，表示不同面额的硬币；以及一个整数 <code>amount</code> ，表示总金额。</p><p>计算并返回可以凑成总金额所需的 <strong>最少的硬币个数</strong> 。如果没有任何一种硬币组合能组成总金额，返回 <code>-1</code> 。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">coinChange</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; coins, <span class="type">int</span> amount)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(amount + <span class="number">1</span>, INT_MAX)</span></span>;</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= amount; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; coin : coins) &#123;</span><br><span class="line">                <span class="keyword">if</span> (coin &gt; i || dp[i - coin] == INT_MAX) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                dp[i] = <span class="built_in">min</span>(dp[i - coin] + <span class="number">1</span>,dp[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> (dp[amount] == INT_MAX) ? <span class="number">-1</span> : dp[amount];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你一个整数数组 <code>coins</code> 表示不同面额的硬币，另给一个整数 <code>amount</code> 表示总金额。</p><p>请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额，返回 <code>0</code> 。</p><p>假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">change</span><span class="params">(<span class="type">int</span> amount, vector&lt;<span class="type">int</span>&gt;&amp; coins)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 方案数</span></span><br><span class="line">        <span class="comment">// 组合 完全背包</span></span><br><span class="line">        <span class="type">int</span> n = coins.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(amount + <span class="number">1</span>)</span></span>; <span class="comment">// 凑到金额i的方案数</span></span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; ++i) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= amount; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (j &gt;= coins[i] &amp;&amp; dp[j]&lt;INT_MAX-dp[j-coins[i]]) &#123;</span><br><span class="line">                    dp[j] += dp[j - coins[i]];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[amount];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">change</span><span class="params">(<span class="type">int</span> amount, vector&lt;<span class="type">int</span>&gt;&amp; coins)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = coins.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// memo[index][target] 存储结果</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">memo</span>(n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(amount + <span class="number">1</span>, <span class="number">-1</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(<span class="number">0</span>, amount, coins, memo);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> index, <span class="type">int</span> target, vector&lt;<span class="type">int</span>&gt;&amp; coins, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; memo)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 基准情况 1：金额正好凑齐</span></span><br><span class="line">        <span class="keyword">if</span> (target == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 基准情况 2：硬币用完了或者金额过大</span></span><br><span class="line">        <span class="keyword">if</span> (index == coins.<span class="built_in">size</span>() || target &lt; <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检查记忆化</span></span><br><span class="line">        <span class="keyword">if</span> (memo[index][target] != <span class="number">-1</span>) <span class="keyword">return</span> memo[index][target];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 决策：</span></span><br><span class="line">        <span class="comment">// 1. 使用当前硬币：dfs(index, target - coins[index])</span></span><br><span class="line">        <span class="comment">// 2. 跳过当前硬币：dfs(index + 1, target)</span></span><br><span class="line">        <span class="type">int</span> res = <span class="built_in">dfs</span>(index, target - coins[index], coins, memo) </span><br><span class="line">                + <span class="built_in">dfs</span>(index + <span class="number">1</span>, target, coins, memo);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> memo[index][target] = res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>解动态规划类问题，分析过程是有章可循的，通过<strong>对阶段、子问题和状态的拆解基本可以得到解决问题的框架</strong>。具体的求解过程一般可以通过推导状态转移方程或填状态转移表这两种方法来实现。</p><p>动态规划问题通常伴随着滚动数组的技巧，从而在空间上达到更优，这正是其相对于记忆化递归而言最大的优点，还有一个好处是动态规划避免了递归产生的额外调用栈的性能开销。</p><p>递归调用子问题时会出现很多重复的子问题计算。一个显而易见的想法是将已经计算过的子问题结果保存起来以备后面使用。如此处理之后，在下一次遇到同样的子问题时直接返回结果可以大大地降低计算的时间复杂度。这种解题思路被称为带“记忆”的递归调用（也被称为自顶向下的动态规划）。</p><p><a href="https://juejin.cn/post/7484471047720206370">动态规划-可重复装背包问题-排列数和组合数理解 2025.3.17完全背包问题中，求组合数是外层遍历物品，内层遍历背包容 - 掘金</a></p><p><a href="https://www.programmercarl.com/背包理论基础01背包-1.html#思路">动态规划：01背包理论基础 | 代码随想录</a></p><p><a href="https://blog.csdn.net/theonepiece/article/details/126227234">背包问题总结【0-1背包、完全背包、排列组合问题】_背包问题 排列 组合-CSDN博客</a></p><h2 id="01背包，完全背包，组合排列与滚动数组"><a href="#01背包，完全背包，组合排列与滚动数组" class="headerlink" title="01背包，完全背包，组合排列与滚动数组"></a>01背包，完全背包，组合排列与滚动数组</h2><p>1 背包问题</p><ul><li><strong>场景</strong>：有 $N$ 件物品和一个容量为 $W$ 的背包。每件物品<strong>仅有一件</strong>，只有“装（1）”或“不装（0）”两种选择。</li><li><strong>核心矛盾</strong>：在容量有限的情况下，如何抉择才能让价值最大。</li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, bagweight;<span class="comment">// bagweight代表行李箱空间</span></span><br><span class="line"></span><br><span class="line">    cin &gt;&gt; n &gt;&gt; bagweight;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">weight</span><span class="params">(n, <span class="number">0</span>)</span></span>; <span class="comment">// 存储每件物品所占空间</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">value</span><span class="params">(n, <span class="number">0</span>)</span></span>;  <span class="comment">// 存储每件物品价值</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; ++i) &#123;</span><br><span class="line">        cin &gt;&gt; weight[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; ++j) &#123;</span><br><span class="line">        cin &gt;&gt; value[j];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// dp数组, dp[i][j]代表行李箱空间为j的情况下,从下标为[0, i]的物品里面任意取,能达到的最大价值</span></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(weight.<span class="built_in">size</span>(), <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(bagweight + <span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化, 因为需要用到dp[i - 1]的值</span></span><br><span class="line">    <span class="comment">// j &lt; weight[0]已在上方被初始化为0</span></span><br><span class="line">    <span class="comment">// j &gt;= weight[0]的值就初始化为value[0]</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = weight[<span class="number">0</span>]; j &lt;= bagweight; j++) &#123;</span><br><span class="line">        dp[<span class="number">0</span>][j] = value[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i &lt; weight.<span class="built_in">size</span>(); i++) &#123; <span class="comment">// 遍历科研物品</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j &lt;= bagweight; j++) &#123; <span class="comment">// 遍历行李箱容量</span></span><br><span class="line">            <span class="keyword">if</span> (j &lt; weight[i]) dp[i][j] = dp[i - <span class="number">1</span>][j]; <span class="comment">// 如果装不下这个物品,那么就继承dp[i - 1][j]的值</span></span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                dp[i][j] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][j], dp[i - <span class="number">1</span>][j - weight[i]] + value[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    cout &lt;&lt; dp[n - <span class="number">1</span>][bagweight] &lt;&lt; endl;</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><strong>倒序遍历是为了保证物品i只被放入一次</strong>。如果一旦正序遍历了，那么物品0就会被重复加入多次</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><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><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 读取 M 和 N</span></span><br><span class="line">    <span class="type">int</span> M, N;</span><br><span class="line">    cin &gt;&gt; M &gt;&gt; N;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">costs</span><span class="params">(M)</span></span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">values</span><span class="params">(M)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; M; i++) &#123;</span><br><span class="line">        cin &gt;&gt; costs[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; M; j++) &#123;</span><br><span class="line">        cin &gt;&gt; values[j];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建一个动态规划数组dp，初始值为0</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 外层循环遍历每个类型的研究材料</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; M; ++i) &#123;</span><br><span class="line">        <span class="comment">// 内层循环从 N 空间逐渐减少到当前研究材料所占空间</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = N; j &gt;= costs[i]; --j) &#123;</span><br><span class="line">            <span class="comment">// 考虑当前研究材料选择和不选择的情况，选择最大值</span></span><br><span class="line">            dp[j] = <span class="built_in">max</span>(dp[j], dp[j - costs[i]] + values[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 输出dp[N]，即在给定 N 行李空间可以携带的研究材料最大价值</span></span><br><span class="line">    cout &lt;&lt; dp[N] &lt;&lt; endl;</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><span class="line"></span><br></pre></td></tr></table></figure><p>完全背包 </p><ul><li><strong>场景</strong>：基本背景相同，但每种物品都有<strong>无限件</strong>。只要背包装得下，你可以一直拿同一种物品。</li><li><strong>核心矛盾</strong>：不再是“拿不拿”，而是“拿几个</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, bagWeight;</span><br><span class="line">    <span class="type">int</span> w, v;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; bagWeight;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">weight</span><span class="params">(n)</span></span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">value</span><span class="params">(n)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        cin &gt;&gt; weight[i] &gt;&gt; value[i];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(bagWeight + <span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = weight[<span class="number">0</span>]; j &lt;= bagWeight; j++)</span><br><span class="line">        dp[<span class="number">0</span>][j] = dp[<span class="number">0</span>][j - weight[<span class="number">0</span>]] + value[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; i++) &#123; <span class="comment">// 遍历物品</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j &lt;= bagWeight; j++) &#123; <span class="comment">// 遍历背包容量</span></span><br><span class="line">            <span class="keyword">if</span> (j &lt; weight[i]) dp[i][j] = dp[i - <span class="number">1</span>][j];</span><br><span class="line">            <span class="keyword">else</span> dp[i][j] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][j], dp[i][j - weight[i]] + value[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; dp[n - <span class="number">1</span>][bagWeight] &lt;&lt; endl;</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><span class="line"></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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> N, bagWeight;</span><br><span class="line">    cin &gt;&gt; N &gt;&gt; bagWeight;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">weight</span><span class="params">(N, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">value</span><span class="params">(N, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; N; i++) &#123;</span><br><span class="line">        <span class="type">int</span> w;</span><br><span class="line">        <span class="type">int</span> v;</span><br><span class="line">        cin &gt;&gt; w &gt;&gt; v;</span><br><span class="line">        weight[i] = w;</span><br><span class="line">        value[i] = v;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(bagWeight + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j &lt;= bagWeight; j++) &#123; <span class="comment">// 遍历背包容量</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; weight.<span class="built_in">size</span>(); i++) &#123; <span class="comment">// 遍历物品</span></span><br><span class="line">            <span class="keyword">if</span> (j - weight[i] &gt;= <span class="number">0</span>) dp[j] = <span class="built_in">max</span>(dp[j], dp[j - weight[i]] + value[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    cout &lt;&lt; dp[bagWeight] &lt;&lt; endl;</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><p>在二维状态 $dp[i][j]$ 中，计算第 $i$ 行只依赖于第 $i-1$ 行。因此我们可以只用一个一维数组 $dp[j]$，通过不断“覆盖”旧值来节省空间</p><p><strong>对于纯完全背包问题，其for循环的先后循环是可以颠倒的</strong></p><p>求组合数 (Combinations) —— 顺序无关</p><ul><li><strong>场景</strong>：硬币 <code>{1, 2}</code> 和 <code>{2, 1}</code> 算同一种。</li><li><strong>秘籍</strong>：<strong>外层遍历物品，内层遍历容量</strong>。</li><li><p><strong>逻辑</strong>：先算完只用硬币 1 的方案，再往里加硬币 2。这保证了硬币 2 只会出现在硬币 1 之后。</p><p>求排列数 (Permutations) —— 顺序有关</p></li><li><p><strong>场景</strong>：硬币 <code>{1, 2}</code> 和 <code>{2, 1}</code> 算两种（比如爬楼梯，先走 1 阶再走 2 阶不同于先 2 后 1）。</p></li><li><strong>秘籍</strong>：<strong>外层遍历容量，内层遍历物品</strong>。</li><li><strong>逻辑</strong>：对于每一个容量 $j$，我们都重新尝试所有的硬币。这样在凑出 3 的时候，既可以从 $dp[3-1]$ 转移（最后一步是 1），也可以从 $dp[3-2]$ 转移（最后一步是 2）。</li></ul><ol><li>物理层：解决“选几次” (01 vs 完全)</li></ol><p>这主要体现在<strong>内层循环的遍历方向</strong>上。</p><p><strong>逆序遍历：解决“01 背包”</strong></p><ul><li><strong>场景</strong>：每个物品只能选 1 次。</li><li><strong>逻辑</strong>：我们需要用“上一行（即不包含当前物品）”的数据。</li><li><strong>原因</strong>：因为是从大到小更新，当我们更新 $dp[j]$ 时，$dp[j - weight]$ 还是旧的值，还没有被当前物品污染过。</li></ul><p><strong>正序遍历：解决“完全背包”</strong></p><ul><li><strong>场景</strong>：每个物品可以选无限次。</li><li><strong>逻辑</strong>：我们需要用“当前行（即已经考虑过放入当前物品）”的数据。</li><li><strong>原因</strong>：从小到大更新，更新 $dp[j]$ 时，$dp[j - weight]$ 可能已经在本轮循环中放入过当前物品了，现在是在它的基础上“再多拿一个”。</li></ul><ol><li>逻辑层：解决“怎么排” (组合 vs 排列)</li></ol><p>这主要体现在<strong>内、外层循环谁包着谁</strong>。</p><p><strong>外层物品，内层容量 $\rightarrow$ 组合 (Combination)</strong></p><ul><li><p><strong>适用问题</strong>：<code>{1, 5}</code> 和 <code>{5, 1}</code> 算同一种方案。</p></li><li><p><strong>代表题</strong>：LeetCode 518. 零钱兑换 II。</p></li><li><p><strong>底层原理</strong>：</p><p>我们是挨个处理物品的。处理完 1 号硬币，再处理 2 号。这就意味着，在任何一个状态下，硬币的编号只能是递增的。你不可能在放入 2 号硬币后的某个状态里，又跑回去拿 1 号硬币。</p><blockquote><p><strong>结论</strong>：这种顺序强制去掉了“乱序”的可能性，只留下唯一的一种组合。</p></blockquote></li></ul><p><strong>外层容量，内层物品 $\rightarrow$ 排列 (Permutation)</strong></p><ul><li><p><strong>适用问题</strong>：<code>{1, 5}</code> 和 <code>{5, 1}</code> 算两种不同的方案。</p></li><li><p><strong>代表题</strong>：LeetCode 377. 组合总和 Ⅳ（虽然叫组合总和，其实求的是排列）。</p></li><li><p><strong>底层原理</strong>：</p><p>对于每一个容量 $j$，我们都把所有物品拿出来试一遍。</p><p>比如容量为 6：</p><ul><li><p>它可以从 $dp[6-1]$ 走一步 1 过来（最后一步是 1）。</p></li><li><p>它可以从 $dp[6-5]$ 走一步 5 过来（最后一步是 5）。</p><p>只要最后一步不同，就是不同的路径。</p></li></ul><blockquote><p><strong>结论</strong>：这种顺序允许同一个容量被不同的“最后一步”推导出来，从而计入所有排列。</p></blockquote></li></ul><p>极值问题：对顺序“迟钝”</p><ul><li><strong>适用问题</strong>：求“最少硬币数”或“最大价值”。</li><li><strong>代表题</strong>：LeetCode 322. 零钱兑换（求最少硬币数）。</li><li><strong>现象</strong>：你会发现，求最少硬币数时，外层是物品还是容量，代码都能过。</li><li><strong>原因</strong>：极值问题（$\min / \max$）只关心<strong>最终结果</strong>，不关心你是通过 <code>{1, 5}</code> 还是 <code>{5, 1}</code> 凑出来的。反正它们凑出来的硬币数都是 2，取 $\min$ 的结果是一样的。</li></ul><p>当你看到一道背包题，请按以下步骤决定你的 <code>for</code> 循环：</p><div class="table-container"><table><thead><tr><th><strong>步骤</strong></th><th><strong>提问</strong></th><th><strong>决定</strong></th></tr></thead><tbody><tr><td><strong>1. 看次数</strong></td><td>物品只能用一次吗？</td><td><strong>是</strong>：内层逆序；<strong>否</strong>：内层正序。</td></tr><tr><td><strong>2. 看性质</strong></td><td>是求最大价值吗？</td><td><strong>是</strong>：外层物品/容量皆可，通常选外层物品（逻辑更顺）。</td></tr><tr><td><strong>3. 看顺序</strong></td><td>是求方案数且 <code>{1,2} != {2,1}</code> 吗？</td><td><strong>是</strong>：外层容量，内层物品。</td></tr><tr><td><strong>4. 看顺序</strong></td><td>是求方案数且 <code>{1,2} == {2,1}</code> 吗？</td><td><strong>是</strong>：外层物品，内层容量。</td></tr></tbody></table></div><p><strong>单词拆分</strong></p><p>给你一个字符串 <code>s</code> 和一个字符串列表 <code>wordDict</code> 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 <code>s</code> 则返回 <code>true</code>。<strong>注意：</strong>不要求字典中出现的单词全部都使用，并且字典中的单词可以重复使用。</p><p>定义 <em>dp</em>[<em>i</em>] 表示字符串 <em>s</em> 前 <em>i</em> 个字符组成的字符串 <em>s</em>[0..<em>i</em>−1] 是否能被空格拆分成若干个字典中出现的单词。</p><p><em>dp</em>[<em>i</em>]=<em>dp</em>[<em>j</em>] &amp;&amp; <em>check</em>(<em>s</em>[<em>j</em>..<em>i</em>−1])</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">wordBreak</span><span class="params">(string s, vector&lt;string&gt;&amp; wordDict)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// dp[j]表示字符串s[0-j]能否被字典中单词拼接</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">dp</span><span class="params">(sz + <span class="number">1</span>)</span></span>;</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">wordSet</span><span class="params">(wordDict.begin(),wordDict.end())</span></span>;</span><br><span class="line">        <span class="comment">// dp[j] = dp[j-word.size()] || word exist</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;=sz;i++) &#123; </span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>;j&lt;i;j++) &#123;</span><br><span class="line">                string word = s.<span class="built_in">substr</span>(j,i-j);</span><br><span class="line">                <span class="keyword">if</span>(wordSet.<span class="built_in">count</span>(word)) &#123;</span><br><span class="line">                    dp[i] = dp[i] || dp[j];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给定一个字符串 <code>s</code> 和一个字符串字典 <code>wordDict</code> ，在字符串 <code>s</code> 中增加空格来构建一个句子，使得句子中所有的单词都在词典中。<strong>以任意顺序</strong> 返回所有这些可能的句子。<strong>注意：</strong>词典中的同一个单词可能在分段中被重复使用多次。</p><p>对于字符串 s，如果某个前缀是单词列表中的单词，则拆分出该单词，然后对 s 的剩余部分继续拆分。如果可以将整个字符串 s 拆分成单词列表中的单词，则得到一个句子。在对 s 的剩余部分拆分得到一个句子之后，将拆分出的第一个单词（即 s 的前缀）添加到句子的头部，即可得到一个完整的句子。上述过程可以通过回溯实现。</p><p>假设字符串 s 的长度为 n，回溯的时间复杂度在最坏情况下高达 O(n )。时间复杂度高的原因是存在大量重复计算，可以通过记忆化的方式降低时间复杂度。</p><p>具体做法是，使用哈希表存储字符串 s 的每个下标和从该下标开始的部分可以组成的句子列表，在回溯过程中如果遇到已经访问过的下标，则可以直接从哈希表得到结果，而不需要重复计算。如果到某个下标发现无法匹配，则哈希表中该下标对应的是空列表，因此可以对不能拆分的情况进行剪枝优化。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, vector&lt;string&gt;&gt; memo;</span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">dfs</span><span class="params">(string&amp; s, <span class="type">int</span> index, unordered_set&lt;string&gt;&amp; wordSet)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (index == s.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;&quot;</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(index)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[index];</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;string&gt; res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = index; j &lt; s.<span class="built_in">size</span>(); j++) &#123;</span><br><span class="line">            string str = s.<span class="built_in">substr</span>(index, j - index + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">if</span> (wordSet.<span class="built_in">count</span>(str)) &#123;</span><br><span class="line">                <span class="keyword">auto</span> ans = <span class="built_in">dfs</span>(s, j + <span class="number">1</span>, wordSet);</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; sstr : ans) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (sstr == <span class="string">&quot;&quot;</span>) &#123;</span><br><span class="line">                        res.<span class="built_in">push_back</span>(str);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        res.<span class="built_in">push_back</span>(str + <span class="string">&quot; &quot;</span> + sstr);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[index] = res;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">vector&lt;string&gt; <span class="title">wordBreak</span><span class="params">(string s, vector&lt;string&gt;&amp; wordDict)</span> </span>&#123;</span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">wordSet</span><span class="params">(wordDict.begin(), wordDict.end())</span></span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(s, <span class="number">0</span>, wordSet);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>股票系列</strong></p><p>给定一个数组 <code>prices</code> ，它的第 <code>i</code> 个元素 <code>prices[i]</code> 表示一支给定股票第 <code>i</code> 天的价格。</p><p>你只能选择 <strong>某一天</strong> 买入这只股票，并选择在 <strong>未来的某一个不同的日子</strong> 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润，返回 <code>0</code> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 最大值减去最小值</span></span><br><span class="line">        <span class="type">int</span> min_price&#123;INT_MAX&#125;;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;prices.<span class="built_in">size</span>();i++) &#123;</span><br><span class="line">            min_price = <span class="built_in">min</span>(min_price,prices[i]);</span><br><span class="line">            ans = <span class="built_in">max</span>(prices[i] - min_price,ans);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>每次遍历，相对于比较当前价格与其前边的每个价格来寻找最大差值max_diff，我们只需要对比前面出现过的那个最小值即可。定义并使用变量min_price来保存遇到的最小价格，将循环内操作的时间复杂度降低至常数阶，算法的总体时间复杂度也就降至为O(n)。</p><p>一个整数数组 <code>prices</code> ，其中 <code>prices[i]</code> 表示某支股票第 <code>i</code> 天的价格。在每一天，你可以决定是否购买和/或出售股票。你在任何时候 <strong>最多</strong> 只能持有 <strong>一股</strong> 股票。然而，你可以在 <strong>同一天</strong> 多次买卖该股票，但要确保你持有的股票不超过一股。返回 <em>你能获得的 <strong>最大</strong> 利润</em> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = prices.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(sz, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(<span class="number">2</span>));</span><br><span class="line">        <span class="comment">// dp[i][0]表示当前不持有的最大利润</span></span><br><span class="line">        <span class="comment">// dp[i][1]表示当前持有的最大利润</span></span><br><span class="line">        dp[<span class="number">0</span>][<span class="number">1</span>] = -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="comment">// 卖出</span></span><br><span class="line">            dp[i][<span class="number">0</span>] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][<span class="number">0</span>], prices[i] + dp[i - <span class="number">1</span>][<span class="number">1</span>]);</span><br><span class="line">            <span class="comment">// 买入</span></span><br><span class="line">            dp[i][<span class="number">1</span>] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][<span class="number">1</span>], -prices[i] + dp[i - <span class="number">1</span>][<span class="number">0</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[sz - <span class="number">1</span>][<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>注意到上面的状态转移方程中，每一天的状态只与前一天的状态有关，而与更早的状态都无关，因此我们不必存储这些无关的状态，只需要将 dp[i−1][0] 和 dp[i−1][1] 存放在两个变量中，通过它们计算出 dp[i][0] 和 dp[i][1] 并存回对应的变量，以便于第 i+1 天的状态转移即可。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = prices.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(sz, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(<span class="number">2</span>));</span><br><span class="line">        <span class="comment">// dp[i][0]表示当前不持有的最大利润</span></span><br><span class="line">        <span class="comment">// dp[i][1]表示当前持有的最大利润</span></span><br><span class="line">        <span class="type">int</span> soldPrice&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> buyPrice = -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">int</span> tmp = soldPrice;</span><br><span class="line">            <span class="comment">// 卖出</span></span><br><span class="line">            soldPrice = <span class="built_in">max</span>(soldPrice,buyPrice+prices[i]);</span><br><span class="line">            <span class="comment">// dp[i][0] = max(dp[i - 1][0], prices[i] + dp[i - 1][1]);</span></span><br><span class="line">            <span class="comment">// 买入</span></span><br><span class="line">            buyPrice = <span class="built_in">max</span>(buyPrice,-prices[i]+tmp);</span><br><span class="line">            <span class="comment">// dp[i][1] = max(dp[i - 1][1], -prices[i] + dp[i - 1][0]);</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> soldPrice;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给定一个整数数组 <code>prices</code>，其中 <code>prices[i]</code>表示第 <code>i</code> 天的股票价格 ；整数 <code>fee</code> 代表了交易股票的手续费用。</p><p>你可以无限次地完成交易，但是你每笔交易都需要付手续费。如果你已经购买了一个股票，在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。</p><p><strong>注意：</strong>这里的一笔交易指买入持有并卖出股票的整个过程，每笔交易你只需要为支付一次手续费。</p><p>考虑 dp[i][0] 的转移方程，如果这一天交易完后手里没有股票，那么可能的转移状态为前一天已经没有股票，即 dp[i−1][0]，或者前一天结束的时候手里持有一支股票，即 dp[i−1][1]，这时候我们要将其卖出，并获得 prices[i] 的收益，但需要支付 fee 的手续费。因此为了收益最大化，我们列出如下的转移方程：</p><p>dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]−fee}<br>再来按照同样的方式考虑 dp[i][1] 按状态转移，那么可能的转移状态为前一天已经持有一支股票，即 dp[i−1][1]，或者前一天结束时还没有股票，即 dp[i−1][0]，这时候我们要将其买入，并减少 prices[i] 的收益。可以列出如下的转移方程：</p><p>dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}<br>对于初始状态，根据状态定义我们可以知道第 0 天交易结束的时候有 dp[0][0]=0 以及 dp[0][1]=−prices[0]。</p><p>因此，我们只要从前往后依次计算状态即可。由于全部交易结束后，持有股票的收益一定低于不持有股票的收益，因此这时候 dp[n−1][0] 的收益必然是大于 dp[n−1][1] 的，最后的答案即为 dp[n−1][0]。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices, <span class="type">int</span> fee)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = prices.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(<span class="number">2</span>));</span><br><span class="line">        dp[<span class="number">0</span>][<span class="number">0</span>] = <span class="number">0</span>, dp[<span class="number">0</span>][<span class="number">1</span>] = -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; ++i) &#123;</span><br><span class="line">            dp[i][<span class="number">0</span>] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][<span class="number">0</span>], dp[i - <span class="number">1</span>][<span class="number">1</span>] + prices[i] - fee);</span><br><span class="line">            dp[i][<span class="number">1</span>] = <span class="built_in">max</span>(dp[i - <span class="number">1</span>][<span class="number">1</span>], dp[i - <span class="number">1</span>][<span class="number">0</span>] - prices[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[n - <span class="number">1</span>][<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//注意到在状态转移方程中，dp[i][0] 和 dp[i][1] 只会从 dp[i−1][0] 和 dp[i−1][1] 转移而来，因此我们不必使用数组存储所有的状态，而是使用两个变量 sell 以及 buy 分别表示 dp[..][0] 和 dp[..][1] 直接进行状态转移即可。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices, <span class="type">int</span> fee)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = prices.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// vector&lt;int&gt; buyPrice(sz);</span></span><br><span class="line">        <span class="type">int</span> soldPrice&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> buyPrice = -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i =<span class="number">1</span>;i&lt;prices.<span class="built_in">size</span>();i++) &#123;</span><br><span class="line">            <span class="comment">// 状态压缩</span></span><br><span class="line">            <span class="type">int</span> newSoldPrice = <span class="built_in">max</span>(soldPrice,buyPrice-fee+prices[i]);</span><br><span class="line">            <span class="type">int</span> newBuyPrice = <span class="built_in">max</span>(buyPrice,soldPrice-prices[i]);</span><br><span class="line">            soldPrice = newSoldPrice;</span><br><span class="line">            buyPrice = newBuyPrice;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> soldPrice;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给定一个整数数组<code>prices</code>，其中第 <code>prices[i]</code> 表示第 <code>*i*</code> 天的股票价格 。</p><p>设计一个算法计算出最大利润。在满足以下约束条件下，你可以尽可能地完成更多的交易（多次买卖一支股票）:卖出股票后，你无法在第二天买入股票 (即冷冻期为 1 天)。</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz= prices.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// vector&lt;vector&lt;int&gt;&gt; dp(sz,vector&lt;int&gt;(3));</span></span><br><span class="line">        <span class="type">int</span> dp0&#123;&#125;,dp1&#123;&#125;,dp2&#123;&#125;;</span><br><span class="line">        <span class="comment">// dp[i][0] 买入状态</span></span><br><span class="line">        <span class="comment">// dp[i][1] 卖出状态 不包括冷冻期</span></span><br><span class="line">        <span class="comment">// dp[i][2] 冷冻期</span></span><br><span class="line">        dp0 = -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;sz;i++) &#123;</span><br><span class="line">            <span class="comment">// 买入状态可由 买入或者冷冻期转换</span></span><br><span class="line">            <span class="comment">// dp[i][0] = max(dp[i-1][0],dp[i-1][2]-prices[i]);</span></span><br><span class="line">            <span class="type">int</span> newDp0 = <span class="built_in">max</span>(dp0,dp2-prices[i]);</span><br><span class="line">            <span class="type">int</span> newDp1 = <span class="built_in">max</span>(dp1,dp0+prices[i]);</span><br><span class="line">            <span class="type">int</span> newDp2 = dp1;</span><br><span class="line">            dp0 = newDp0;</span><br><span class="line">            dp1 = newDp1;</span><br><span class="line">            dp2 = newDp2;</span><br><span class="line">            <span class="comment">// 卖出状态可由买入或者卖出转换</span></span><br><span class="line">            <span class="comment">// dp[i][1] = max(dp[i-1][1],dp[i-1][0]+prices[i]);</span></span><br><span class="line">            <span class="comment">// 冷冻期收益就是前一天卖出时收益</span></span><br><span class="line">            <span class="comment">// dp[i][2] = dp[i-1][1];</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(dp1,dp2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你一个整数数组 <code>prices</code> 和一个整数 <code>k</code> ，其中 <code>prices[i]</code> 是某支给定的股票在第 <code>i</code> 天的价格。</p><p>设计一个算法来计算你所能获取的最大利润。你最多可以完成 <code>k</code> 笔交易。也就是说，你最多可以买 <code>k</code> 次，卖 <code>k</code> 次。<strong>注意：</strong>你不能同时参与多笔交易（你必须在再次购买前出售掉之前的股票）。</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(<span class="type">int</span> k, vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (prices.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> n = prices.<span class="built_in">size</span>();</span><br><span class="line">        k = <span class="built_in">min</span>(k, n / <span class="number">2</span>);</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">buy</span>(n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(k + <span class="number">1</span>, <span class="number">-1e9</span>));</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">sell</span>(n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(k + <span class="number">1</span>, <span class="number">-1e9</span>));</span><br><span class="line">        <span class="keyword">if</span> (k &gt;= <span class="number">1</span>) &#123;</span><br><span class="line">            buy[<span class="number">0</span>][<span class="number">1</span>] = -prices[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        sell[<span class="number">0</span>][<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; ++i) &#123;</span><br><span class="line">            sell[i][<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= k; ++j) &#123;</span><br><span class="line">                buy[i][j] = <span class="built_in">max</span>(buy[i - <span class="number">1</span>][j], sell[i - <span class="number">1</span>][j - <span class="number">1</span>] - prices[i]);</span><br><span class="line">                sell[i][j] = <span class="built_in">max</span>(sell[i - <span class="number">1</span>][j], buy[i - <span class="number">1</span>][j] + prices[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 最终结果在最后一天完成各种交易次数中的最大值</span></span><br><span class="line">        <span class="keyword">return</span> *<span class="built_in">max_element</span>(sell[n - <span class="number">1</span>].<span class="built_in">begin</span>(), sell[n - <span class="number">1</span>].<span class="built_in">end</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每一笔交易都包含“买入”和“卖出”两个动作。对于最多 $k$ 次交易，我们在每一天可能处于以下 $2k$ 种状态之一：</p><ul><li><strong><code>buy[j]</code></strong>：第 $j$ 次持有股票（已经买入，还没卖出）。</li><li><strong><code>sell[j]</code></strong>：第 $j$ 次卖出股票（手里是空的，且已经完成了 $j$ 次交易）。</li></ul><p>这里 $j$ 的取值范围是 $1$ 到 $k$。</p><p>对于每一天的价格 <code>price</code>，我们要更新这 $2k$ 个状态：</p><ol><li><p><strong>第 $j$ 次持有 (<code>buy[j]</code>)</strong>：</p><ul><li>保持现状：昨天就持有第 $j$ 次的股票。</li><li>今天买入：由<strong>上一次交易完成</strong>的状态 <code>sell[j-1]</code> 减去今天的价格。</li><li>公式：<code>buy[j] = max(buy[j], sell[j-1] - price)</code></li></ul></li><li><p><strong>第 $j$ 次卖出 (<code>sell[j]</code>)</strong>：</p><ul><li>保持现状：昨天就已经完成了第 $j$ 次交易。</li><li>今天卖出：由<strong>本次持有</strong>的状态 <code>buy[j]</code> 加上今天的价格。</li><li>公式：<code>sell[j] = max(sell[j], buy[j] + price)</code></li></ul><p>核心思维是一致的：用状态记录你的“处境”，用转移方程描述“动作”。</p></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(<span class="type">int</span> k, vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = prices.<span class="built_in">size</span>() ;</span><br><span class="line">        k = <span class="built_in">min</span>(k, sz/<span class="number">2</span>);</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">buy</span><span class="params">(k + <span class="number">1</span>, INT_MIN)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sell</span><span class="params">(k + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= k; j++) &#123;</span><br><span class="line">                buy[j] = <span class="built_in">max</span>(sell[j - <span class="number">1</span>] - prices[i], buy[j]);</span><br><span class="line">                sell[j] = <span class="built_in">max</span>(prices[i] + buy[j], sell[j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sell[k];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给定一个数组，它的第 <code>i</code> 个元素是一支给定的股票在第 <code>i</code> 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 <strong>两笔</strong> 交易。<strong>注意：</strong>你不能同时参与多笔交易（你必须在再次购买前出售掉之前的股票）。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = prices.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> buy1 = -prices[<span class="number">0</span>], sell1 = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> buy2 = -prices[<span class="number">0</span>], sell2 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; ++i) &#123;</span><br><span class="line">            buy1 = <span class="built_in">max</span>(buy1, -prices[i]);</span><br><span class="line">            sell1 = <span class="built_in">max</span>(sell1, buy1 + prices[i]);</span><br><span class="line">            buy2 = <span class="built_in">max</span>(buy2, sell1 - prices[i]);</span><br><span class="line">            sell2 = <span class="built_in">max</span>(sell2, buy2 + prices[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sell2;</span><br><span class="line">    &#125;</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_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; prices)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">buy</span><span class="params">(<span class="number">3</span>, INT_MIN)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sell</span><span class="params">(<span class="number">3</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; prices.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= <span class="number">2</span>; j++) &#123;</span><br><span class="line">                buy[j] = <span class="built_in">max</span>(buy[j], -prices[i] + sell[j - <span class="number">1</span>]);</span><br><span class="line">                sell[j] = <span class="built_in">max</span>(sell[j], prices[i] + buy[j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sell[<span class="number">2</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你两个下标从 <strong>0</strong> 开始的数组 <code>present</code> 和 <code>future</code> ，<code>present[i]</code> 和 <code>future[i]</code> 分别代表第 <code>i</code> 支股票现在和将来的价格。每支股票你最多购买 <strong>一次</strong> ，你的预算为 <code>budget</code> 。求最大的收益。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maximumProfit</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; present, vector&lt;<span class="type">int</span>&gt;&amp; future, <span class="type">int</span> budget)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 0/1背包问题</span></span><br><span class="line">        <span class="comment">// 容量为i时最大收益</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(budget<span class="number">+1</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> sz = present.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">value</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i =<span class="number">0</span>;i&lt;sz;i++) &#123;</span><br><span class="line">            value[i] = future[i] - present[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;sz;i++) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = budget;j&gt;=<span class="number">0</span>;j--) &#123;</span><br><span class="line">                <span class="keyword">if</span>(j&gt;=present[i]) &#123;</span><br><span class="line">                    dp[j] = <span class="built_in">max</span>(dp[j],dp[j-present[i]]+value[i]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[budget];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>当股票收益最大时，应购买哪些股票？如果有多种方案，返回其中任意一种。</p><p>要找出具体的股票清单（路径回溯），我们不能直接使用状态压缩后的一维数组，因为一维数组丢弃了“这个最优值是从哪个物品转移过来”的信息。我们需要使用 <strong>二维数组 <code>dp[i][j]</code></strong>。</p><h4 id="回溯算法："><a href="#回溯算法：" class="headerlink" title="回溯算法："></a>回溯算法：</h4><ol><li><p>从最后一支股票 $n$ 和总预算 <code>budget</code> 开始。</p></li><li><p>如果 $dp[i][j] == dp[i-1][j]$，说明第 $i$ 支股票<strong>没买</strong>，继续检查 $dp[i-1][j]$。</p></li><li><p>如果 $dp[i][j] \neq dp[i-1][j]$，说明第 $i$ 支股票<strong>买了</strong>，记录下标 $i$，然后剩余预算变为 $j - \text{present}[i]$，继续检查 $dp[i-1][j - \text{present}[i]]$。</p></li><li><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    pair&lt;<span class="type">int</span>, vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">getStocks</span>(vector&lt;<span class="type">int</span>&gt;&amp; present, vector&lt;<span class="type">int</span>&gt;&amp; future, <span class="type">int</span> budget) &#123;</span><br><span class="line">        <span class="type">int</span> n = present.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// dp[i][j] 表示前 i 支股票在预算 j 下的最大收益</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(budget + <span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; ++i) &#123;</span><br><span class="line">            <span class="type">int</span> cost = present[i<span class="number">-1</span>];</span><br><span class="line">            <span class="type">int</span> profit = <span class="built_in">max</span>(<span class="number">0</span>, future[i<span class="number">-1</span>] - present[i<span class="number">-1</span>]);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt;= budget; ++j) &#123;</span><br><span class="line">                <span class="keyword">if</span> (j &gt;= cost) &#123;</span><br><span class="line">                    dp[i][j] = <span class="built_in">max</span>(dp[i<span class="number">-1</span>][j], dp[i<span class="number">-1</span>][j - cost] + profit);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    dp[i][j] = dp[i<span class="number">-1</span>][j];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 回溯找出购买的股票下标</span></span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; selectedStocks;</span><br><span class="line">        <span class="type">int</span> curBudget = budget;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = n; i &gt;= <span class="number">1</span>; --i) &#123;</span><br><span class="line">            <span class="comment">// 如果当前最大收益不等于“不买这支股票时的收益”，说明买了</span></span><br><span class="line">            <span class="keyword">if</span> (dp[i][curBudget] &gt; dp[i<span class="number">-1</span>][curBudget]) &#123;</span><br><span class="line">                selectedStocks.<span class="built_in">push_back</span>(i - <span class="number">1</span>); <span class="comment">// 存入下标</span></span><br><span class="line">                curBudget -= present[i<span class="number">-1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> &#123;dp[n][budget], selectedStocks&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ol><h1 id="滑动窗口"><a href="#滑动窗口" class="headerlink" title="滑动窗口"></a>滑动窗口</h1><p>滑动窗口是双指针的特殊应用，该题型本身并不复杂，但有些具体细节需要注意。滑动窗口常用于解决数组、字符串的子元素问题，它可以将嵌套的循环展开，通过减少内层循环次数来降低算法的时间复杂度。滑动窗口类题目通常需要用到双指针，还可能用到其他的数据结构，比如哈希表、队列。</p><p>按照滑动窗口的窗口大小是否固定，以及可变窗口中求最大窗口还是最小窗口，可以分类如下。</p><p>●　固定窗口类型</p><p>●　可变窗口类型。此类题目不会给出窗口大小，而是求符合条件的最大窗口或最小窗口。</p><p>➢　求最大窗口。例如第424题。</p><p>➢　求最小窗口。例如第72题。</p><p>通常会使用双指针来界定窗口的边界，两个指针之间的部分属于窗口内，反之属于窗口外。固定窗口类型的题目，两个指针要同时移动；而可变窗口类型的题目，则移动其中一个指针来实现窗口大小的变化。</p><p>滑动窗口类型的题目是有“套路”可循的，用两个指针分别表示窗口的左右端点，然后右指针不断地去扩充右侧窗口边界，左指针不断地缩小左边窗口边界，同时维护窗口的信息，在这个过程中不断判断窗口信息是否满足条件，如果是，则更新答案；如果不是，则继续移动窗口（收缩、扩展或平移）。</p><p><strong>滑动窗口最大值</strong></p><p>给你一个整数数组 <code>nums</code>，有一个大小为 <code>k</code> 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 <code>k</code> 个数字。滑动窗口每次只向右移动一位。</p><p>返回 <em>滑动窗口中的最大值</em></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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">maxSlidingWindow</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 固定窗口</span></span><br><span class="line">        <span class="comment">// 每次加入一个值 删除一个值</span></span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">        list&lt;<span class="type">int</span>&gt; lst; <span class="comment">// 维护最大值</span></span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;k;i++) &#123;</span><br><span class="line">            <span class="keyword">while</span>(!lst.<span class="built_in">empty</span>() &amp;&amp; nums[i]&gt;lst.<span class="built_in">back</span>()) &#123;</span><br><span class="line">                lst.<span class="built_in">pop_back</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            lst.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        res.<span class="built_in">push_back</span>(lst.<span class="built_in">front</span>());</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = k;i&lt;sz;i++) &#123;</span><br><span class="line">            <span class="keyword">while</span>(!lst.<span class="built_in">empty</span>() &amp;&amp; nums[i]&gt;lst.<span class="built_in">back</span>()) &#123;</span><br><span class="line">                lst.<span class="built_in">pop_back</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            lst.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            <span class="keyword">if</span>(nums[i-k] == lst.<span class="built_in">front</span>()) &#123;</span><br><span class="line">                lst.<span class="built_in">pop_front</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            res.<span class="built_in">push_back</span>(lst.<span class="built_in">front</span>());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>最小覆盖子串</strong></p><p>给定两个字符串 <code>s</code> 和 <code>t</code>，长度分别是 <code>m</code> 和 <code>n</code>，返回 s 中的 <strong>最短窗口 子串</strong>，使得该子串包含 <code>t</code> 中的每一个字符（<strong>包括重复字符</strong>）。如果没有这样的子串，返回空字符串 <code>&quot;&quot;</code>。</p><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">minWindow</span><span class="params">(string s, string t)</span> </span>&#123;</span><br><span class="line">        unordered_map&lt;<span class="type">char</span>, <span class="type">int</span>&gt; cnts;</span><br><span class="line">        <span class="type">int</span> targetCnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : t) &#123;</span><br><span class="line">            cnts[ch]++;</span><br><span class="line">            targetCnt++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 计算窗口内有效字符</span></span><br><span class="line">        <span class="type">int</span> slen&#123;INT_MAX&#125;;</span><br><span class="line">        <span class="type">int</span> start_pos&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> totalCnt&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> left&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (cnts.<span class="built_in">count</span>(s[i])) &#123;</span><br><span class="line">                <span class="comment">// 还有剩余字符</span></span><br><span class="line">                <span class="keyword">if</span> (cnts[s[i]] &gt;= <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="comment">// 剩余字符个数大于0，有效字符+1</span></span><br><span class="line">                    totalCnt++;</span><br><span class="line">                &#125;</span><br><span class="line">                cnts[s[i]]--;</span><br><span class="line">                <span class="keyword">while</span> (left &lt;= i &amp;&amp; totalCnt == targetCnt) &#123;</span><br><span class="line">                    <span class="type">int</span> len = i - left + <span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">if</span> (len &lt; slen) &#123;</span><br><span class="line">                        start_pos = left;</span><br><span class="line">                        slen = len;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (cnts.<span class="built_in">count</span>(s[left])) &#123;</span><br><span class="line">                        <span class="keyword">if</span> (cnts[s[left]] &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                            totalCnt--;</span><br><span class="line">                        &#125;</span><br><span class="line">                        cnts[s[left]]++;</span><br><span class="line">                    &#125;</span><br><span class="line">                    left++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> INT_MAX == slen ? <span class="string">&quot;&quot;</span> : s.<span class="built_in">substr</span>(start_pos, slen);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>替换后的最长重复字符</strong></p><p>你一个字符串 <code>s</code> 和一个整数 <code>k</code> 。你可以选择字符串中的任一字符，并将其更改为任何其他大写英文字符。该操作最多可执行 <code>k</code> 次。在执行上述操作后，返回 <em>包含相同字母的最长子字符串的长度</em></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">characterReplacement</span><span class="params">(string s, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 可以跳过k次</span></span><br><span class="line">        unordered_map&lt;<span class="type">char</span>, <span class="type">int</span>&gt; cnts;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> left&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> max_cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            cnts[s[i]]++;</span><br><span class="line">            max_cnt = <span class="built_in">max</span>(max_cnt, cnts[s[i]]);</span><br><span class="line">            <span class="keyword">while</span>(left&lt;=i &amp;&amp; i-left<span class="number">+1</span>&gt;max_cnt+k) &#123;</span><br><span class="line">                <span class="comment">//移动左窗口  不满足要求</span></span><br><span class="line">                cnts[s[left]]--;</span><br><span class="line">                left++;</span><br><span class="line">            &#125;</span><br><span class="line">            ans = <span class="built_in">max</span>(ans,i-left<span class="number">+1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>字符串的排列</strong></p><p>给你两个字符串 <code>s1</code> 和 <code>s2</code> ，写一个函数来判断 <code>s2</code> 是否包含 <code>s1</code> 的 排列。如果是，返回 <code>true</code> ；否则，返回 <code>false</code> 。换句话说，<code>s1</code> 的排列之一是 <code>s2</code> 的 <strong>子串</strong> </p><p>此问题的关键点是，如果能在S2中找到一个子串的长度与S1相等，并且S1中每个字符对应的个数与S2中这个子串的每个字符对应的个数相等，那S2就一定包含S1的一个排列。因此可以使用与S1等长的滑动窗口，判断S2在这个窗口内的字符出现个数和S1的字符出现个数是否相等。又因为题目中给出了条件，所有的字符都是小写字母，因此可以通过哈希表来统计每个字符出现的个数。</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">checkInclusion</span><span class="params">(string s1, string s2)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = s<span class="number">1.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span>(sz&gt;s<span class="number">2.</span><span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">cnts</span><span class="params">(<span class="number">26</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : s1) &#123;</span><br><span class="line">            cnts[ch - <span class="string">&#x27;a&#x27;</span>]++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">targets</span><span class="params">(<span class="number">26</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s<span class="number">1.</span><span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            targets[s2[i] - <span class="string">&#x27;a&#x27;</span>]++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (targets == cnts) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = sz; i &lt; s<span class="number">2.</span><span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            targets[s2[i] - <span class="string">&#x27;a&#x27;</span>]++;</span><br><span class="line">            targets[s2[i - sz] - <span class="string">&#x27;a&#x27;</span>]--;</span><br><span class="line">            <span class="keyword">if</span> (targets == cnts) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>滑动窗口大多用于解决数组、字符串、链表的子区间题型，如果题目中出现求解子数组、子串，或者寻找符合某个特征的子数组、子串问题，就可以考虑使用滑动窗口的方法。滑动窗口问题的优化思路主要包括：是否可以通过某个条件来缩减检测的子序列个数；是否可以通过使用特定的数据结构（如字典）来降低检测子序列的某个特征是否符合条件的复杂度。其中，缩减子序列的方法包括改变序列的大小、边界的提前退出等。</p><p><img data-src="https://files.seeusercontent.com/2026/02/18/jy3S/image-20260218195651652.png" alt="image-20260218195651652"></p><h1 id="博弈问题"><a href="#博弈问题" class="headerlink" title="博弈问题"></a>博弈问题</h1><p><strong>石子游戏</strong></p><p>lice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子，<strong>排成一行</strong>；每堆都有 <strong>正</strong> 整数颗石子，数目为 <code>piles[i]</code> 。游戏以谁手中的石子最多来决出胜负。石子的 <strong>总数</strong> 是 <strong>奇数</strong> ，所以没有平局。</p><p>Alice 和 Bob 轮流进行，<strong>Alice 先开始</strong> 。 每回合，玩家从行的 <strong>开始</strong> 或 <strong>结束</strong> 处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止，此时手中 <strong>石子最多</strong> 的玩家 <strong>获胜</strong> 。</p><p>假设 Alice 和 Bob 都发挥出最佳水平，当 Alice 赢得比赛时返回 <code>true</code> ，当 Bob 赢得比赛时返回 <code>false</code></p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p><strong>预测赢家</strong></p><p>给你一个整数数组 <code>nums</code> 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。</p><p>玩家 1 和玩家 2 轮流进行自己的回合，玩家 1 先手。开始时，两个玩家的初始分值都是 <code>0</code> 。每一回合，玩家从数组的任意一端取一个数字（即，<code>nums[0]</code> 或 <code>nums[nums.length - 1]</code>），取到的数字将会从数组中移除（数组长度减 <code>1</code> ）。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时，游戏结束。</p><p>如果玩家 1 能成为赢家，返回 <code>true</code> 。如果两个玩家得分相等，同样认为玩家 1 是游戏的赢家，也返回 <code>true</code> 。你可以假设每个玩家的玩法都会使他的分数最大化。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">predictTheWinner</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// dp[i][j]表示还剩nums[i-j]时最大差异</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(n,<span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = n<span class="number">-2</span>;i&gt;=<span class="number">0</span>;i--) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = i;j&lt;n;j++) &#123;</span><br><span class="line">                <span class="keyword">if</span>(j == i) &#123;</span><br><span class="line">                    dp[i][j] = nums[i];</span><br><span class="line">                &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                    dp[i][j] = <span class="built_in">max</span>(nums[i] - dp[i<span class="number">+1</span>][j],nums[j] - dp[i][j<span class="number">-1</span>]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">0</span>][n<span class="number">-1</span>]&gt;=<span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>猜数字游戏</strong></p><p>我们正在玩一个猜数游戏，游戏规则如下：</p><ol><li>我从 <code>1</code> 到 <code>n</code> 之间选择一个数字。</li><li>你来猜我选了哪个数字。</li><li>如果你猜到正确的数字，就会 <strong>赢得游戏</strong> 。</li><li>如果你猜错了，那么我会告诉你，我选的数字比你的 <strong>更大或者更小</strong> ，并且你需要继续猜数。</li><li>每当你猜了数字 <code>x</code> 并且猜错了的时候，你需要支付金额为 <code>x</code> 的现金。如果你花光了钱，就会 <strong>输掉游戏</strong> 。</li></ol><p>给你一个特定的数字 <code>n</code> ，返回能够 <strong>确保你获胜</strong> 的最小现金数，<strong>不管我选择那个数字</strong> </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getMoneyAmount</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(<span class="number">1</span> + n, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(<span class="number">1</span> + n));</span><br><span class="line">        <span class="comment">// dp[i][j]</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = n - <span class="number">1</span>; i &gt;= <span class="number">1</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span> + i; j &lt;= n; j++) &#123;</span><br><span class="line">                dp[i][j] = j+dp[i][j<span class="number">-1</span>];</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> k = i; k &lt; j; k++) &#123;</span><br><span class="line">                    dp[i][j] =</span><br><span class="line">                        <span class="built_in">min</span>(dp[i][j], k + <span class="built_in">max</span>(dp[i][k - <span class="number">1</span>], dp[k + <span class="number">1</span>][j]));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">1</span>][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>实现方面，创建行数和列数都是 n+1 的二维数组 f，其中 f[i][j] 即为状态 f(i,j)。在根据状态转移方程计算时需要注意下标的边界问题，当 j=n 时，如果 k=j 则 k+1&gt;n，此时 f[k][j] 会出现下标越界。为了避免出现下标越界，计算 f[i][j] 的方法是：首先令 f[i][j]=j+f[i][j−1]，然后遍历 i≤k&lt;j 的每个 k，更新 f[i][j] 的值。</p><p><img data-src="https://files.seeusercontent.com/2026/02/19/oZg6/image-20260219112111053.png" alt="image-20260219112111053"></p><h1 id="分治法"><a href="#分治法" class="headerlink" title="分治法"></a>分治法</h1><p>分治法是一种很重要的算法，属于五大常用算法之一。其字面意思是“分而治之”，具体可以分为3个步骤。　</p><p>“分”指的是将一个复杂的问题分成多个性质相同但规模更小的子问题，而子问题同样能够继续分解直到能够被解决。　</p><ol><li>“治”指的是对子问题分别进行处理。</li></ol><p>“合”就是将子问题的解进行合并，从而得到原问题的解。</p><p>与动态规划一样，分治法很大程度上也基于递归的思想，两者的区别在于动态规划分解后的子问题是有重复的（重叠子问题性质），而分治法的子问题通常不会重复。因此，分治法所能解决的问题一般具有以下几个特征。1．问题的规模缩小到一定程度后可以被很容易地解决。</p><p>2．问题可以分解为若干个规模较小的相同性质的问题。</p><p>3．问题的解等于子问题解的合并。</p><p>4．问题分解的各个子问题相互独立，没有重复。</p><p>上述前3点决定了问题能否通过分治法来解决。而最后一点涉及分治法的效率，原因在于如果各个子问题不相互独立，则会产生重复的工作，此时虽然可以使用分治法，但使用动态规划效率会更高。</p><p>下面我们列出常见的可以使用分治法的经典问题</p><p>●　二分搜索。</p><p>●　大整数的乘法。</p><p>核心思路（分→治→合）</p><p>传统大数乘法（逐位相乘再相加）时间复杂度是 O(n2)，而 Karatsuba 算法通过分治将复杂度降到 O(nlog23)≈O(n1.585)，核心步骤：</p><ol><li><p>分（Divide）</p><p>设两个 n 位大数 x和 y，拆分为高位和低位：x = a <em> 10^(n/2) + b，y = c </em> 10^(n/2) + d</p><p>（比如 x=1234，拆为 a=12，b=34；y=5678，拆为 c=56，d=78）</p></li><li><p>治（Conquer）</p><p>递归计算 3 个子问题（而非传统的 4 个，这是优化核心）：</p><ul><li><code>ac = 分治乘法(a, c)</code></li><li><code>bd = 分治乘法(b, d)</code></li><li>ad_bc = 分治乘法(a+b, c+d) - ac - bd</li></ul></li><li><p>合（Combine）</p><p>原结果 = ac <em> 10^n + ad_bc </em> 10^(n/2) + bd</p></li></ol><figure class="highlight python"><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 class="keyword">def</span> <span class="title function_">karatsuba</span>(<span class="params">x: <span class="built_in">int</span>, y: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="comment"># 基准情况：数字足够小时直接相乘</span></span><br><span class="line">    <span class="keyword">if</span> x &lt; <span class="number">10</span> <span class="keyword">or</span> y &lt; <span class="number">10</span>:</span><br><span class="line">        <span class="keyword">return</span> x * y</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 确定拆分的位数（取两个数的最大位数的一半）</span></span><br><span class="line">    n = <span class="built_in">max</span>(<span class="built_in">len</span>(<span class="built_in">str</span>(x)), <span class="built_in">len</span>(<span class="built_in">str</span>(y)))</span><br><span class="line">    half = n // <span class="number">2</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 拆分x和y为高位a/b，低位c/d</span></span><br><span class="line">    a, b = <span class="built_in">divmod</span>(x, <span class="number">10</span>**half)</span><br><span class="line">    c, d = <span class="built_in">divmod</span>(y, <span class="number">10</span>**half)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 递归计算3个子问题</span></span><br><span class="line">    ac = karatsuba(a, c)</span><br><span class="line">    bd = karatsuba(b, d)</span><br><span class="line">    ad_bc = karatsuba(a + b, c + d) - ac - bd</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 合并结果</span></span><br><span class="line">    <span class="keyword">return</span> ac * <span class="number">10</span>**(<span class="number">2</span>*half) + ad_bc * <span class="number">10</span>**half + bd</span><br></pre></td></tr></table></figure><p>●　strassen矩阵乘法。</p><p><img data-src="https://files.seeusercontent.com/2026/02/23/8Lrb/image-20260223131535419.png" alt="image-20260223131535419"></p><p>●　棋盘覆盖问题。●　归并排序和快速排序。●　最接近点对问题。●　汉诺塔问题。</p><p><strong>合并K个升序链表</strong></p><p>给你一个链表数组，每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中，返回合并后的链表.</p><p>用分治的方法进行合并。</p><p>将 k 个链表配对并将同一对中的链表合并；<br>第一轮合并以后， k 个链表被合并成了 k/2个链表,然后是k/4个链表等等.</p><p>核心逻辑：</p><ul><li>将 <code>lists</code> 分为左右两半。</li><li>递归处理左半部分，递归处理右半部分。</li><li>最后调用“合并两个有序链表”的函数将两部分合并</li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">mergeTwoLists</span><span class="params">(ListNode* node1, ListNode* node2)</span> </span>&#123;</span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        ListNode* cur = dummy;</span><br><span class="line">        <span class="keyword">while</span> (node1 &amp;&amp; node2) &#123;</span><br><span class="line">            <span class="keyword">if</span> (node1-&gt;val &lt; node2-&gt;val) &#123;</span><br><span class="line">                cur-&gt;next = node1;</span><br><span class="line">                node1 = node1-&gt;next;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                cur-&gt;next = node2;</span><br><span class="line">                node2 = node2-&gt;next;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (node1) &#123;</span><br><span class="line">            cur-&gt;next = node1;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (node2) &#123;</span><br><span class="line">            cur-&gt;next = node2;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">ListNode* <span class="title">mergeLists</span><span class="params">(<span class="type">int</span> left, <span class="type">int</span> right, vector&lt;ListNode*&gt;&amp; lists)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (left &gt; right) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (left == right) &#123;</span><br><span class="line">            <span class="keyword">return</span> lists[left];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">        ListNode* l1 = <span class="built_in">mergeLists</span>(left, mid, lists);</span><br><span class="line">        ListNode* l2 = <span class="built_in">mergeLists</span>(mid + <span class="number">1</span>, right, lists);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">mergeTwoLists</span>(l1, l2);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">ListNode* <span class="title">mergeKLists</span><span class="params">(vector&lt;ListNode*&gt;&amp; lists)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = lists.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (sz == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* res = <span class="built_in">mergeLists</span>(<span class="number">0</span>, sz - <span class="number">1</span>, lists);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">mergeKLists</span><span class="params">(vector&lt;ListNode*&gt;&amp; lists)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">auto</span> comp = [](ListNode* a, ListNode* b) &#123; <span class="keyword">return</span> a-&gt;val &gt; b-&gt;val; &#125;;</span><br><span class="line">        <span class="comment">// 最小堆</span></span><br><span class="line">        priority_queue&lt;ListNode*, vector&lt;ListNode*&gt;, <span class="keyword">decltype</span>(comp)&gt; <span class="built_in">pq</span>(comp);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span> l : lists) &#123;</span><br><span class="line">            <span class="keyword">if</span> (l) &#123;</span><br><span class="line">                pq.<span class="built_in">push</span>(l);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode *dummy = <span class="keyword">new</span> ListNode, *cur = dummy;</span><br><span class="line">        <span class="keyword">while</span> (!pq.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            cur-&gt;next = pq.<span class="built_in">top</span>();</span><br><span class="line">            pq.<span class="built_in">pop</span>();</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">            <span class="keyword">if</span> (cur-&gt;next) &#123;</span><br><span class="line">                pq.<span class="built_in">push</span>(cur-&gt;next);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>数组中的第K个最大元素</strong></p><p>给定整数数组 <code>nums</code> 和整数 <code>k</code>，请返回数组中第 <code>**k**</code> 个最大的元素。请注意，你需要找的是数组排序后的第 <code>k</code> 个最大的元素，而不是第 <code>k</code> 个不同的元素。你必须设计并实现时间复杂度为 <code>O(n)</code> 的算法解决此问题。</p><p>在计算机科学中，快速选择（quick select）作为一种在无序列表中获得第k小元素的选择算法，是分治思想的经典应用之一。它和快速排序（quick sort）算法一样，都来自Tony Hoare，因此也被称为Hoare’s selection algorithm。快速排序算法在实践中拥有非常好的平均性能，很多工业界的排序算法都有其身影（比如各种高级编程语言官方库中的排序方法）。与快速排序算法思想相近的快速选择算法及其变种，同样拥有非常好的平均性能，但缺点也是类似的，即该算法在最坏情况下性能很差。回归到题目，<strong>求第k大元素可以直接转换成求第n-k+1小的元素，这里的n是数组的大小，也就是说我们可以在这里使用快速选择算法</strong>。</p><p>速选择算法和快速排序算法共用了partition子过程，也就是分治法中的分解操作。而两者之间的区别在于，快速排序算法会将问题划分为两个子问题分开递归解决，其只需要递归处理一个子问题即可。快速选择算法的逻辑很简单，如下所示。1．随机选择一个pivot（支点）。2．使用partition子过程将pivot放在数组中合适的位置，将其设为pos。partition的作用就是将小于pivot的元素移到左边，大于或等于pivot的元素移到右边。</p><p>partition之后，我们先判断pos是否是想要的结果，如果是，则直接得到答案（这里也是上面讲述分治法时提到的可以直接求解的最简单子问题）。</p><p>如果不是，判断答案是在pos的左边还是右边，然后在新的范围中重复步骤1和2。这就是将问题拆分成子问题，并对子问题进行递归处理的过程。这里随机选择一个pivot是为了尽量降低快速选择算法中最坏情况发生的可能性。另外一种技巧是随机化打乱数组的预处理。</p><p>通常来讲，有两种partition方式：Lomuto partition和Hoare partition。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">partition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> left, <span class="type">int</span> right, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (left == right) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[left];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> i = left - <span class="number">1</span>, j = right + <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> pivot = nums[left];</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                i++;</span><br><span class="line">            &#125; <span class="keyword">while</span> (nums[i] &lt; pivot);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                j--;</span><br><span class="line">            &#125; <span class="keyword">while</span> (nums[j] &gt; pivot);</span><br><span class="line">            <span class="keyword">if</span> (i &gt;= j) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">swap</span>(nums[i], nums[j]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (k &lt;= j) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">partition</span>(nums, left, j, k);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">partition</span>(nums, j + <span class="number">1</span>, right, k);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findKthLargest</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">partition</span>(nums, <span class="number">0</span>, nums.<span class="built_in">size</span>() - <span class="number">1</span>, nums.<span class="built_in">size</span>() - k);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Hoare 分区的特点是：</p><ul><li>基准值最终可能在任意位置</li><li>只保证 <code>left..j</code>的元素 ≤ 基准值</li><li>只保证 <code>j+1..right</code>的元素 ≥ 基准值  </li><li>为什么 Hoare 分区可以用于快速选择？关键在于：不需要知道 pivot 的确切位置，只需要知道目标元素 <code>k</code>在哪一边分区即可。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 分区后：j 是分界点</span></span><br><span class="line"><span class="comment">// [left .. j] [j+1 .. right]</span></span><br><span class="line"><span class="comment">//   ≤ pivot     ≥ pivot</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (k &lt;= j) search left;   <span class="comment">// 注意：包含 j！</span></span><br><span class="line"><span class="keyword">else</span> search right;</span><br></pre></td></tr></table></figure><p>lumoto分区特点. <strong>基准值放在正确位置</strong>，对重复元素处理较差</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="comment">// 分区后：pivot 在最终位置 p</span></span><br><span class="line"><span class="comment">// [left .. p-1] [p] [p+1 .. right]</span></span><br><span class="line"><span class="comment">//   ≤ pivot     pivot   ≥ pivot</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (k == p) <span class="keyword">return</span> nums[p];</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (k &lt; p) search left;</span><br><span class="line"><span class="keyword">else</span> search right;  <span class="comment">// 注意：k &gt; p</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">lomutoPartition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> pivot = arr[high]; <span class="comment">// 选最后一位</span></span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = low; j &lt; high; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[j] &lt;= pivot) &#123;</span><br><span class="line">            i++;</span><br><span class="line">            <span class="built_in">swap</span>(arr[i], arr[j]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">swap</span>(arr[i + <span class="number">1</span>], arr[high]); <span class="comment">// 把 pivot 放到中间</span></span><br><span class="line">    <span class="keyword">return</span> i + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">quickSelect2</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp;nums,<span class="type">int</span> l,<span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> target = nums[l];</span><br><span class="line">        <span class="type">int</span> i = l,j = r;</span><br><span class="line">        <span class="keyword">while</span>(i&lt;j) &#123;</span><br><span class="line">            <span class="keyword">while</span>(i&lt;j &amp;&amp; nums[j]&gt;=target) &#123;</span><br><span class="line">                j--;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">while</span>(i&lt;j &amp;&amp; nums[i]&lt;=target) &#123;</span><br><span class="line">                i++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(i&gt;=j) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="built_in">swap</span>(nums[i],nums[j]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">swap</span>(nums[i],nums[l]);</span><br><span class="line">        <span class="keyword">return</span> i;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">hoarePartition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> pivot = arr[low]; <span class="comment">// 选第一位</span></span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> j = high + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="keyword">do</span> &#123; i++; &#125; <span class="keyword">while</span> (arr[i] &lt; pivot);</span><br><span class="line">        <span class="keyword">do</span> &#123; j--; &#125; <span class="keyword">while</span> (arr[j] &gt; pivot);</span><br><span class="line">        <span class="keyword">if</span> (i &gt;= j) <span class="keyword">return</span> j;</span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[j]);</span><br><span class="line">    &#125;</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ====================== 1. Lomuto 分区（带随机基准） ======================</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">lomutoPartition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 步骤1：随机选择基准值，交换到右边界（Lomuto默认选右边界）</span></span><br><span class="line">    <span class="type">int</span> randIdx = low + <span class="built_in">rand</span>() % (high - low + <span class="number">1</span>); <span class="comment">// 生成[low, high]的随机数</span></span><br><span class="line">    <span class="built_in">swap</span>(arr[randIdx], arr[high]);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 步骤2：标准 Lomuto 分区逻辑</span></span><br><span class="line">    <span class="type">int</span> pivot = arr[high]; <span class="comment">// 基准值（已交换到右边界）</span></span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;       <span class="comment">// 左区（&lt;=pivot）的右边界</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = low; j &lt; high; ++j) &#123;</span><br><span class="line">        <span class="comment">// 把&lt;=pivot的元素归集到左区</span></span><br><span class="line">        <span class="keyword">if</span> (arr[j] &lt;= pivot) &#123;</span><br><span class="line">            ++i;</span><br><span class="line">            <span class="built_in">swap</span>(arr[i], arr[j]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 步骤3：将基准值放到分界点（i+1）</span></span><br><span class="line">    <span class="built_in">swap</span>(arr[i + <span class="number">1</span>], arr[high]);</span><br><span class="line">    <span class="keyword">return</span> i + <span class="number">1</span>; <span class="comment">// 返回基准值的位置（分界点）</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ====================== 2. Hoare 分区（带随机基准） ======================</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">hoarePartition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; arr, <span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 步骤1：随机选择基准值，交换到左边界（Hoare示例选左边界）</span></span><br><span class="line">    <span class="type">int</span> randIdx = low + <span class="built_in">rand</span>() % (high - low + <span class="number">1</span>);</span><br><span class="line">    <span class="built_in">swap</span>(arr[randIdx], arr[low]);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 步骤2：标准 Hoare 分区逻辑</span></span><br><span class="line">    <span class="type">int</span> pivot = arr[low]; <span class="comment">// 基准值（已交换到左边界）</span></span><br><span class="line">    <span class="type">int</span> i = low - <span class="number">1</span>;      <span class="comment">// 左指针（初始左移一位，避免漏判）</span></span><br><span class="line">    <span class="type">int</span> j = high + <span class="number">1</span>;     <span class="comment">// 右指针（初始右移一位，避免漏判）</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">// 左指针右移：找 &gt; pivot 的元素（适配找第k大，左区存更大值）</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            ++i;</span><br><span class="line">        &#125; <span class="keyword">while</span> (arr[i] &lt; pivot);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 右指针左移：找 &lt; pivot 的元素</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            --j;</span><br><span class="line">        &#125; <span class="keyword">while</span> (arr[j] &gt; pivot);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 指针相遇，分区结束</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt;= j) &#123;</span><br><span class="line">            <span class="keyword">return</span> j; <span class="comment">// 返回分界点（左区[low,j] &gt;= pivot，右区[j+1,high] &lt;= pivot）</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 交换违规元素</span></span><br><span class="line">        <span class="built_in">swap</span>(arr[i], arr[j]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>Lomuto 分区</strong></th><th><strong>Hoare 分区</strong></th></tr></thead><tbody><tr><td><strong>指针方向</strong></td><td>同向移动（都向右）</td><td><strong>对向移动</strong>（向中间靠拢）</td></tr><tr><td><strong>代码简洁度</strong></td><td>非常简洁</td><td>略复杂（有死循环风险，需谨慎处理）</td></tr><tr><td><strong>交换频率</strong></td><td>高</td><td><strong>低（效率更高）</strong></td></tr><tr><td><strong>Pivot 位置</strong></td><td>结束后 Pivot 就在最终排序位置</td><td>结束后 Pivot 可能不在最终位置</td></tr><tr><td><strong>重复元素处理</strong></td><td>较差（容易造成分区不平衡）</td><td><strong>更好</strong>（能更均匀地分配重复元素）</td></tr></tbody></table></div><p><strong>搜索二维矩阵</strong></p><p>编写一个高效的算法来搜索 <code>*m* x *n*</code> 矩阵 <code>matrix</code> 中的一个目标值 <code>target</code> 。该矩阵具有以下特性：</p><ul><li>每行的元素从左到右升序排列。</li><li>每列的元素从上到下升序排列。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">searchValue</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix, <span class="type">int</span> target, <span class="type">int</span> row,</span></span></span><br><span class="line"><span class="params"><span class="function">                     <span class="type">int</span> col)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (row &gt;= matrix.<span class="built_in">size</span>() || col &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (matrix[row][col] == target) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (matrix[row][col] &lt; target) &#123;</span><br><span class="line">            <span class="comment">// 行数增加</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">searchValue</span>(matrix, target, row + <span class="number">1</span>, col);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 列数减小</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">searchValue</span>(matrix, target, row, col - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">searchMatrix</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">searchValue</span>(matrix, target, <span class="number">0</span>, n - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果能够满足本章开头说的分治法特征，就可以尝试使用分治法来处理，具体如下所示。●　问题的规模缩小到一定程度后可以被很容易地解决。●　问题可以分解为若干个规模较小的相同性质的问题。●　问题的解等于子问题解的合并。●　问题分解的各个子问题相互独立，没有重复。而在具体使用分治法时，紧紧抓住“分解”“解决”和“合并”这3个步骤</p><p><a href="https://leetcode.cn/problems/median-of-two-sorted-arrays/">寻找两个正序数组的中位数</a></p><p>给定两个大小分别为 <code>m</code> 和 <code>n</code> 的正序（从小到大）数组 <code>nums1</code> 和 <code>nums2</code>。请你找出并返回这两个正序数组的 <strong>中位数</strong> 。算法的时间复杂度应该为 <code>O(log (m+n))</code> 。</p><p>要达到 O (log (m+n)) 的时间复杂度，必须使用<strong>二分查找</strong>而非简单的合并数组（合并数组的时间复杂度是 O (m+n)）。核心思路是：</p><ol><li>将问题转化为<strong>寻找两个有序数组中的第 k 小元素</strong>（中位数本质上就是第 (m+n)/2 小的元素）。</li><li>通过二分法不断缩小搜索范围：每次比较两个数组中第 k/2 位置的元素，排除不可能包含第 k 小元素的部分，直到 k=1 时直接取较小值。</li><li>处理奇偶情况：如果 m+n 是奇数，中位数就是第 (m+n+1)/2 小的元素；如果是偶数，就是第 (m+n)/2 和 (m+n)/2 +1 小的元素的平均值。</li></ol><p>如何找第k小？</p><p>假设我们要找第 k 小元素：</p><ol><li>比较两个数组的第 <code>k/2</code>个元素</li><li>较小的那个数组的前 <code>k/2</code>个元素都不可能是第 k 小</li><li>排除这些元素，问题规模减小</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">double</span> <span class="title">findMedianSortedArrays</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums1, vector&lt;<span class="type">int</span>&gt;&amp; nums2)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = nums<span class="number">1.</span><span class="built_in">size</span>(), n = nums<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> total = m + n;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 中位数可能是两个数（偶数长度）或一个数（奇数长度）</span></span><br><span class="line">        <span class="keyword">if</span> (total % <span class="number">2</span> == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 奇数：第 k 小，k = (total+1)/2</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">findKth</span>(nums1, <span class="number">0</span>, nums2, <span class="number">0</span>, (total + <span class="number">1</span>) / <span class="number">2</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 偶数：第 k 小和第 k+1 小的平均值，k = total/2</span></span><br><span class="line">            <span class="type">int</span> left = <span class="built_in">findKth</span>(nums1, <span class="number">0</span>, nums2, <span class="number">0</span>, total / <span class="number">2</span>);</span><br><span class="line">            <span class="type">int</span> right = <span class="built_in">findKth</span>(nums1, <span class="number">0</span>, nums2, <span class="number">0</span>, total / <span class="number">2</span> + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span> (left + right) / <span class="number">2.0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 寻找两个有序数组的第 k 小元素</span></span><br><span class="line">    <span class="comment">// i, j 分别是 nums1 和 nums2 的起始位置</span></span><br><span class="line">    <span class="function"><span class="type">double</span> <span class="title">findKth</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums1, <span class="type">int</span> i, vector&lt;<span class="type">int</span>&gt;&amp; nums2, <span class="type">int</span> j, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = nums<span class="number">1.</span><span class="built_in">size</span>(), n = nums<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果一个数组为空，直接从另一个数组取第k个</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt;= m) <span class="keyword">return</span> nums2[j + k - <span class="number">1</span>];</span><br><span class="line">        <span class="keyword">if</span> (j &gt;= n) <span class="keyword">return</span> nums1[i + k - <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果 k=1，返回两个数组首元素的最小值</span></span><br><span class="line">        <span class="keyword">if</span> (k == <span class="number">1</span>) <span class="keyword">return</span> <span class="built_in">min</span>(nums1[i], nums2[j]);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 计算每个数组应该比较的位置</span></span><br><span class="line">        <span class="comment">// 防止越界：如果剩余长度不足 k/2，就取剩余长度</span></span><br><span class="line">        <span class="type">int</span> mid1 = <span class="built_in">min</span>(i + k / <span class="number">2</span> - <span class="number">1</span>, m - <span class="number">1</span>);</span><br><span class="line">        <span class="type">int</span> mid2 = <span class="built_in">min</span>(j + k / <span class="number">2</span> - <span class="number">1</span>, n - <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 比较两个数组第 k/2 个元素</span></span><br><span class="line">        <span class="keyword">if</span> (nums1[mid1] &lt;= nums2[mid2]) &#123;</span><br><span class="line">            <span class="comment">// 说明 nums1 的前 k/2 个元素都不可能是第 k 小</span></span><br><span class="line">            <span class="comment">// 排除掉 nums1[i...mid1] 这些元素</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">findKth</span>(nums1, mid1 + <span class="number">1</span>, nums2, j, k - (mid1 - i + <span class="number">1</span>));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 排除掉 nums2[j...mid2] 这些元素</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">findKth</span>(nums2, i, nums2, mid2 + <span class="number">1</span>, k - (mid2 - j + <span class="number">1</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在成千上万个文件中找第 $k$ 大的数</p><p>这个问题将之前的“有序数组排除法”推向了<strong>分布式计算</strong>和<strong>大数据处理</strong>的实战领域。</p><p>当数据量大到无法装入内存，且分布在成千上万个文件中时，我们不能再依赖简单的指针移动，而要利用<strong>“值域划分”</strong>或<strong>“分桶统计”</strong>的思想。这本质上是把“对索引的二分”转变成了<strong>“对数值范围的二分”</strong>。</p><p>以下是三种最主流的解决策略：</p><ol><li>桶计数法（最推荐：MapReduce 思想）</li></ol><p>这是处理海量数据最稳健的方法。它的核心是将“寻找第 <em>k</em> 大”转化为“统计落在各个区间的数字个数”。</p><p>步骤：</p><ol><li><strong>确定值域</strong>：假设数字是 32 位整数，范围是 [0,232−1]。</li><li><strong>分桶</strong>：将这个范围均匀分成 <em>M</em> 个桶（例如 1024 个桶）。每个桶代表一个数值区间（如桶 0 代表 [0,4×106]）。</li><li><strong>第一轮扫描（Map）</strong>：遍历成千上万个文件，统计落入每个桶中的数字总数。<ul><li><em>结果示例</em>：桶 0 有 10 亿个数，桶 1 有 5 亿个数……</li></ul></li><li><strong>定位目标桶（Reduce）</strong>：从大到小累加桶的计数。<ul><li>如果前 2 个桶的总数小于 <em>k</em>，但前 3 个桶的总数大于等于 <em>k</em>，那么第 <em>k</em> 大的数一定在<strong>桶 2</strong> 中。</li></ul></li><li><p><strong>精细化搜索</strong>：</p><ul><li>如果桶 2 中的数据量已经足够小（能装进内存），直接读入内存用 <code>QuickSelect</code> 解决。</li><li>如果依然太大，对桶 2 的值域范围再次进行“分桶”，重复上述过程。</li></ul></li><li><p>外部快速选择（External QuickSelect）</p></li></ol><p>这种方法是 <code>QuickSelect</code> 的分布式版本，适合数据分布在多台服务器上的场景。</p><p>步骤：</p><ol><li><strong>选基准值（Pivot）</strong>：从随机一个文件中选一个数作为 Pivot。</li><li><strong>分布式分区</strong>：所有节点同时扫描自己负责的文件，将数字分为“大于 Pivot”、“等于 Pivot”和“小于 Pivot”三类，并记录每类的<strong>总数</strong>。</li><li><strong>判断路径</strong>：<ul><li>如果“大于 Pivot”的总数 ≥<em>k</em>，则第 <em>k</em> 大在这一半，抛弃其余数据，在这一半中继续找。</li><li>如果总数 &lt;<em>k</em> 且（大于+等于）的总数 ≥<em>k</em>，则 Pivot 就是答案。</li><li>否则，去“小于 Pivot”的那一半找第 <em>k</em>−(大于+等于) 大。</li></ul></li></ol><p><strong>缺点</strong>：如果 Pivot 选得不好（比如选到了极值），需要频繁扫描磁盘，I/O 开销极大。</p><ol><li>堆排序法（仅限 <em>k</em> 较小时）</li></ol><p>如果你要找的是“前 1000 大”，而不是“中位数”或“第 10 亿大”，那么堆是无敌的。</p><p>步骤：</p><ol><li>建立一个大小为 <em>k</em> 的<strong>最小堆</strong>。</li><li>逐个读取文件中的数字：<ul><li>如果数字比堆顶大，弹出堆顶，压入新数。</li></ul></li><li>扫描完所有文件后，堆顶即为所求。</li></ol><p><strong>优点</strong>：只需要扫描一遍数据，内存占用极低（仅 <em>O</em>(<em>k</em>)）。</p><p><strong>计算右侧小于当前元素的个数</strong></p><p>给你一个整数数组 <code>nums</code> ，按要求返回一个新数组 <code>counts</code> 。数组 <code>counts</code> 有该性质： <code>counts[i]</code> 的值是 <code>nums[i]</code> 右侧小于 <code>nums[i]</code> 的元素的数量。</p><p>在归并排序的“合并（Merge）”阶段，我们将两个有序的子数组合并。假设左子数组为 <code>L</code>，右子数组为 <code>R</code>。</p><ul><li>当我们从 <code>L</code> 中取出一个元素放入结果数组时，如果 <code>R</code> 中已经有若干个元素先被放入了结果数组，那么<strong>这些先被放入的 <code>R</code> 中的元素，一定是原数组中位于该 <code>L</code> 元素右侧且比它小的数</strong>。</li><li>我们只需要在合并时，记录 <code>R</code> 中有多少个元素已经被移动到了 <code>L</code> 元素的前面即可。</li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">merge</span><span class="params">(vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt;&amp; nums, <span class="type">int</span> left, <span class="type">int</span> right,</span></span></span><br><span class="line"><span class="params"><span class="function">               vector&lt;<span class="type">int</span>&gt;&amp; cnt)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (left &gt;= right)</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">        <span class="built_in">merge</span>(nums, left, mid, cnt);</span><br><span class="line">        <span class="built_in">merge</span>(nums, <span class="number">1</span> + mid, right, cnt);</span><br><span class="line">        vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; <span class="built_in">tmp</span>(right - left + <span class="number">1</span>);</span><br><span class="line">        <span class="type">int</span> i = left, j = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> rightCount&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> idx&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (i &lt;= mid &amp;&amp; j &lt;= right) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nums[i].first &lt;= nums[j].first) &#123;</span><br><span class="line">                cnt[nums[i].second] += rightCount;</span><br><span class="line">                tmp[idx++] = nums[i++];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 大于</span></span><br><span class="line">                rightCount++;</span><br><span class="line">                tmp[idx++] = nums[j++];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 处理左半部分剩余元素 设置左半部分元素的count</span></span><br><span class="line">        <span class="keyword">while</span> (i &lt;= mid) &#123;</span><br><span class="line">            cnt[nums[i].second] += rightCount;</span><br><span class="line">            tmp[idx++] = nums[i++];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 处理右半部分剩余元素</span></span><br><span class="line">        <span class="keyword">while</span> (j &lt;= right) &#123;</span><br><span class="line">            tmp[idx++] = nums[j++];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = left; i &lt;= right; i++) &#123;</span><br><span class="line">            nums[i] = tmp[i - left];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">countSmaller</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; index_nums;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            index_nums.<span class="built_in">push_back</span>(&#123;nums[i], i&#125;); <span class="comment">// 值和对应位置</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">cnt</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="built_in">merge</span>(index_nums, <span class="number">0</span>, sz - <span class="number">1</span>, cnt);</span><br><span class="line">        <span class="keyword">return</span> cnt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>交易逆序对的总数</strong></p><p>在股票交易中，如果前一天的股价高于后一天的股价，则可以认为存在一个「交易逆序对」。请设计一个程序，输入一段时间内的股票交易记录 <code>record</code>，返回其中存在的「交易逆序对」总数</p><ol><li><strong>逆序对定义</strong>：对于 <code>i &lt; j</code>，如果 <code>record[i] &gt; record[j]</code>，则 <code>(i, j)</code>是一个逆序对</li><li><strong>归并排序法的核心思想</strong>：<ul><li>分治：将数组分成两半</li><li>合并时统计：如果左半部分的元素大于右半部分的元素，则左半部分剩余的所有元素都与右半部分的该元素构成逆序对</li><li>时间复杂度：O(nlogn)，空间复杂度：O(n)</li></ul></li><li><strong>为什么归并排序法有效</strong>：<ul><li>在合并两个有序子数组时，可以高效地统计跨越两个子数组的逆序对</li><li>递归地统计每个子数组内部的逆序对</li></ul></li></ol><p>这是面试中常见的问题，<strong>归并排序法</strong>是必须掌握的标准解法。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">reversePairs</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; record)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = record.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (n &lt; <span class="number">2</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">temp</span><span class="params">(n)</span></span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">mergeSort</span>(record, temp, <span class="number">0</span>, n - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">mergeSort</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, vector&lt;<span class="type">int</span>&gt;&amp; temp, <span class="type">int</span> left, <span class="type">int</span> right)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (left &gt;= right) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> mid = left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">int</span> count = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 分治</span></span><br><span class="line">        count += <span class="built_in">mergeSort</span>(nums, temp, left, mid);</span><br><span class="line">        count += <span class="built_in">mergeSort</span>(nums, temp, mid + <span class="number">1</span>, right);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 合并并计数</span></span><br><span class="line">        <span class="type">int</span> i = left, j = mid + <span class="number">1</span>, k = left;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 先统计逆序对</span></span><br><span class="line">        <span class="keyword">while</span> (i &lt;= mid &amp;&amp; j &lt;= right) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nums[i] &gt; nums[j]) &#123;</span><br><span class="line">                <span class="comment">// nums[i] &gt; nums[j]，那么 nums[i..mid] 都 &gt; nums[j]</span></span><br><span class="line">                count += (mid - i + <span class="number">1</span>);</span><br><span class="line">                j++;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                i++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 再真正合并（也可以边统计边合并）</span></span><br><span class="line">        i = left, j = mid + <span class="number">1</span>, k = left;</span><br><span class="line">        <span class="keyword">while</span> (i &lt;= mid &amp;&amp; j &lt;= right) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nums[i] &lt;= nums[j]) &#123;</span><br><span class="line">                temp[k++] = nums[i++];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                temp[k++] = nums[j++];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (i &lt;= mid) temp[k++] = nums[i++];</span><br><span class="line">        <span class="keyword">while</span> (j &lt;= right) temp[k++] = nums[j++];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> idx = left; idx &lt;= right; idx++) &#123;</span><br><span class="line">            nums[idx] = temp[idx];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>漂亮数组</strong></p><p>如果长度为 <code>n</code> 的数组 <code>nums</code> 满足下述条件，则认为该数组是一个 <strong>漂亮数组</strong> ：</p><ul><li><code>nums</code> 是由范围 <code>[1, n]</code> 的整数组成的一个排列。</li><li>对于每个 <code>0 &lt;= i &lt; j &lt; n</code> ，均不存在下标 <code>k</code>（<code>i &lt; k &lt; j</code>）使得 <code>2 * nums[k] == nums[i] + nums[j]</code> 。</li></ul><p>给你整数 <code>n</code> ，返回长度为 <code>n</code> 的任一 <strong>漂亮数组</strong> 。本题保证对于给定的 <code>n</code> 至少存在一个有效答案。</p><p>通过以下步骤递归构造长度为 $n$ 的漂亮数组：</p><ol><li><strong>分</strong>：将 $n$ 个数分成两部分，左边是 $(n+1)/2$ 个数，右边是 $n/2$ 个数。</li><li><strong>变换</strong>：<ul><li>左边由长度为 $(n+1)/2$ 的漂亮数组通过 $2x - 1$ 变换得到（映射为奇数）。</li><li>右边由长度为 $n/2$ 的漂亮数组通过 $2x$ 变换得到（映射为偶数）。</li></ul></li><li><strong>合</strong>：将左右两部分拼接。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, vector&lt;<span class="type">int</span>&gt;&gt; memo;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dfs</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(n)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[n];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 将n分为两半 (n+1)/2 n/2</span></span><br><span class="line">        <span class="comment">// 左半部分全为奇数</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">res</span><span class="params">(n)</span></span>;</span><br><span class="line">        <span class="type">int</span> i&#123;&#125;;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; l = <span class="built_in">dfs</span>((n + <span class="number">1</span>) / <span class="number">2</span>);</span><br><span class="line">        <span class="comment">// 进行转换</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; n : l) &#123;</span><br><span class="line">            <span class="comment">// 全转为奇数</span></span><br><span class="line">            res[i++] = <span class="number">2</span> * n - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r = <span class="built_in">dfs</span>(n / <span class="number">2</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; n : r) &#123;</span><br><span class="line">            <span class="comment">// 全转为偶数</span></span><br><span class="line">            res[i++] = <span class="number">2</span> * n;</span><br><span class="line">        &#125;</span><br><span class="line">        memo[n] = res;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">beautifulArray</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        memo[<span class="number">1</span>] = &#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(n);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="贪心"><a href="#贪心" class="headerlink" title="贪心"></a>贪心</h1><p>每次根据问题的当前状态，选择一个局部最优策略，并且能够不断迭代，最后产生一个全局最优解。换句话说，每次都是从当前问题出发，而不考虑之前或之后的问题的状态，然后做出一个最有利于当前问题的决策，迭代更新问题，不断重复同样的操作直到问题得到解决，此时得到的解为全局最优解。</p><p>一般而言，贪心法的题目只要求我们想到一个合理的局部最优策略，并且通过自己举例测试局部最优策略是否会出问题即可，而不需要去关注如何证明这个策略能够产生一个全局最优解。</p><p><strong>分发饼干</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findContentChildren</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; g, vector&lt;<span class="type">int</span>&gt;&amp; s)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">sort</span>(g.<span class="built_in">begin</span>(), g.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">sort</span>(s.<span class="built_in">begin</span>(), s.<span class="built_in">end</span>());</span><br><span class="line">        <span class="type">int</span> i&#123;&#125;, j&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (i &lt; g.<span class="built_in">size</span>() &amp;&amp; j &lt; s.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (g[i] &lt;= s[j]) &#123;</span><br><span class="line">                <span class="comment">// 满足胃口</span></span><br><span class="line">                i++;</span><br><span class="line">                j++;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 不满足</span></span><br><span class="line">                j++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> i;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>跳跃游戏</strong></p><p>给你一个非负整数数组 <code>nums</code> ，你最初位于数组的 <strong>第一个下标</strong> 。数组中的每个元素代表你在该位置可以跳跃的最大长度。</p><p>判断你是否能够到达最后一个下标，如果可以，返回 <code>true</code> ；否则，返回 <code>false</code> 。</p><p>依次遍历数组中的每一个位置，并实时维护 最远可以到达的位置。对于当前遍历到的位置 x，如果它在 最远可以到达的位置 的范围内，那么我们就可以从起点通过若干次跳跃到达该位置，因此我们可以用 x+nums[x] 更新 最远可以到达的位置。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">canJump</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> max_step&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &lt;= max_step) &#123;</span><br><span class="line">                max_step = <span class="built_in">max</span>(max_step, nums[i] + i);</span><br><span class="line">                <span class="keyword">if</span> (max_step &gt;= nums.<span class="built_in">size</span>() - <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>任务调度器</strong></p><p>给你一个用字符数组 <code>tasks</code> 表示的 CPU 需要执行的任务列表，用字母 A 到 Z 表示，以及一个冷却时间 <code>n</code>。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成，但有一个限制：两个 <strong>相同种类</strong> 的任务之间必须有长度为 <code>n</code> 的冷却时间。返回完成所有任务所需要的 <strong>最短时间间隔</strong> 。</p><p>假设出现次数最多的任务是 <code>A</code>，它的频率为 <code>max_freq</code>。 为了让时间最短，我们应该尽可能把 <code>A</code> 均匀地排开，中间填入冷却时间或其它任务。</p><p>计算出的结果可能面临两种情况：</p><ul><li><p><strong>情况 A：空位不够填</strong></p><p>如果任务种类非常多，导致空位被填满后还有多余任务。此时，我们不需要任何额外的冷却时间（Idle time），总时间就是任务的总数 <code>tasks.size()</code>。</p></li><li><p><strong>情况 B：空位填不满</strong></p><p>如果冷却时间 $n$ 很大，而任务种类很少，空位填不满，必须插入 <code>idle</code>。此时答案就是上面的公式结果。</p></li></ul><p><strong>结论</strong>：最终结果是 $\max(\text{tasks.size()}, (max_f - 1) \times (n + 1) + max_count)$。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">leastInterval</span><span class="params">(vector&lt;<span class="type">char</span>&gt;&amp; tasks, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">cnts</span><span class="params">(<span class="number">26</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> max_cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; ch:tasks) &#123;</span><br><span class="line">            cnts[ch-<span class="string">&#x27;A&#x27;</span>]++;</span><br><span class="line">            max_cnt = <span class="built_in">max</span>(max_cnt,cnts[ch-<span class="string">&#x27;A&#x27;</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> max_chars&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;<span class="number">26</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">if</span>(cnts[i] == max_cnt) &#123;</span><br><span class="line">                max_chars++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;        </span><br><span class="line">        <span class="comment">// 选择频率最多的字符</span></span><br><span class="line">        <span class="comment">// 该字符每个一组,</span></span><br><span class="line">        <span class="type">int</span> res = (n<span class="number">+1</span>)*(max_cnt<span class="number">-1</span>) + max_chars;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>&lt;<span class="type">int</span>&gt;(tasks.<span class="built_in">size</span>(),res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>分发糖果</strong></p><p><code>n</code> 个孩子站成一排。给你一个整数数组 <code>ratings</code> 表示每个孩子的评分。</p><p>你需要按照以下要求，给这些孩子分发糖果：</p><ul><li>每个孩子至少分配到 <code>1</code> 个糖果。</li><li>相邻两个孩子中，评分更高的那个会获得更多的糖果。</li></ul><p>请你给每个孩子分发糖果，计算并返回需要准备的 <strong>最少糖果数目</strong> 。</p><p>我们可以将「相邻的孩子中，评分高的孩子必须获得更多的糖果」这句话拆分为两个规则，分别处理。</p><p>左规则：当 ratings[i−1]&lt;ratings[i] 时，i 号学生的糖果数量将比 i−1 号孩子的糖果数量多。</p><p>右规则：当 ratings[i]&gt;ratings[i+1] 时，i 号学生的糖果数量将比 i+1 号孩子的糖果数量多。</p><p>我们遍历该数组两次，处理出每一个学生分别满足左规则或右规则时，最少需要被分得的糖果数量。每个人最终分得的糖果数量即为这两个数量的最大值。</p><p>具体地，以左规则为例：我们从左到右遍历该数组，假设当前遍历到位置 i，如果有 ratings[i−1]&lt;ratings[i] 那么 i 号学生的糖果数量将比 i−1 号孩子的糖果数量多，我们令 left[i]=left[i−1]+1 即可，否则我们令 left[i]=1。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">candy</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; ratings)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = ratings.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">left</span><span class="params">(sz, <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">right</span><span class="params">(sz, <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (ratings[i] &gt; ratings[i - <span class="number">1</span>]) &#123;</span><br><span class="line">                left[i] = left[i - <span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> ans&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = sz - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (ratings[i] &gt; ratings[i + <span class="number">1</span>]) &#123;</span><br><span class="line">                right[i] = right[i + <span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>;i&lt;sz;i++) &#123;</span><br><span class="line">            ans += <span class="built_in">max</span>(left[i],right[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>无重叠区间</strong></p><p>给定一个区间的集合 <code>intervals</code> ，其中 <code>intervals[i] = [starti, endi]</code> 。返回 <em>需要移除区间的最小数量，使剩余区间互不重叠</em> 。</p><p><strong>注意</strong> 只在一点上接触的区间是 <strong>不重叠的</strong>。例如 <code>[1, 2]</code> 和 <code>[2, 3]</code> 是不重叠的</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">eraseOverlapIntervals</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; intervals)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">sort</span>(intervals.<span class="built_in">begin</span>(), intervals.<span class="built_in">end</span>());</span><br><span class="line">        <span class="comment">// [1,2] [1,3] [2,3] [3,4]</span></span><br><span class="line">        <span class="type">int</span> lastTime = intervals[<span class="number">0</span>][<span class="number">1</span>];</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; intervals.<span class="built_in">size</span>() - <span class="number">1</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (lastTime &lt;= intervals[i + <span class="number">1</span>][<span class="number">0</span>]) &#123;</span><br><span class="line">                lastTime = intervals[i + <span class="number">1</span>][<span class="number">1</span>];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 重叠</span></span><br><span class="line">                lastTime = <span class="built_in">min</span>(lastTime, intervals[i + <span class="number">1</span>][<span class="number">1</span>]);</span><br><span class="line">                res++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">eraseOverlapIntervals</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; intervals)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">auto</span> comp = [](vector&lt;<span class="type">int</span>&gt;&amp; a,vector&lt;<span class="type">int</span>&gt;&amp; b)&#123;</span><br><span class="line">            <span class="keyword">return</span> a[<span class="number">1</span>]&lt;b[<span class="number">1</span>];</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="built_in">sort</span>(intervals.<span class="built_in">begin</span>(),intervals.<span class="built_in">end</span>(),comp);</span><br><span class="line">        <span class="type">int</span> lastTime = intervals[<span class="number">0</span>][<span class="number">1</span>];</span><br><span class="line">        <span class="type">int</span> cnt&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;intervals.<span class="built_in">size</span>();i++) &#123;</span><br><span class="line">            <span class="keyword">if</span>(lastTime&lt;=intervals[i][<span class="number">0</span>]) &#123;</span><br><span class="line">                <span class="comment">// 不需要移除</span></span><br><span class="line">                cnt++;</span><br><span class="line">                lastTime = intervals[i][<span class="number">1</span>];</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="comment">// 大于开始时间 需要移除</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> intervals.<span class="built_in">size</span>()-cnt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>跳跃游戏II</strong></p><p>给定一个长度为 <code>n</code> 的 <strong>0 索引</strong>整数数组 <code>nums</code>。初始位置在下标 0。</p><p>每个元素 <code>nums[i]</code> 表示从索引 <code>i</code> 向后跳转的最大长度。换句话说，如果你在索引 <code>i</code> 处，你可以跳转到任意 <code>(i + j)</code> 处：</p><ul><li><code>0 &lt;= j &lt;= nums[i]</code> 且</li><li><code>i + j &lt; n</code></li></ul><p>返回到达 <code>n - 1</code> 的最小跳跃次数。测试用例保证可以到达 <code>n - 1</code>。</p><p>目标是：<strong>在当前跳跃能达到的范围内，寻找下一跳能跳得最远的位置</strong></p><p>当你从索引 <code>i</code> 遍历到 <code>end</code> 之间时，你会不断更新 <code>farthest</code>（即 <code>max(farthest, i + nums[i])</code>）。当你真正走到 <code>end</code> 这个边界时，说明你<strong>不得不</strong>再跳一次了。此时，你把跳跃次数 <code>jumps</code> 加 1，并将新的边界 <code>end</code> 设置为刚才探测到的 <code>farthest</code>。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">jump</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> jumps = <span class="number">0</span>;      <span class="comment">// 跳跃次数</span></span><br><span class="line">        <span class="type">int</span> farthest = <span class="number">0</span>;   <span class="comment">// 目前能跳到的最远位置</span></span><br><span class="line">        <span class="type">int</span> end = <span class="number">0</span>;        <span class="comment">// 当前跳跃步数能覆盖的边界</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 注意：我们不需要遍历最后一个元素，因为题目保证能到达</span></span><br><span class="line">        <span class="comment">// 如果遍历到最后一个元素，可能会多算一次跳跃</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">            <span class="comment">// 1. 在当前范围内，探测下一跳最远能到哪</span></span><br><span class="line">            farthest = <span class="built_in">max</span>(farthest, i + nums[i]);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 2. 到达当前跳跃的边界了</span></span><br><span class="line">            <span class="keyword">if</span> (i == end) &#123;</span><br><span class="line">                jumps++;</span><br><span class="line">                end = farthest; <span class="comment">// 开启下一段势力范围</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 如果已经能覆盖终点，可以提前结束</span></span><br><span class="line">                <span class="keyword">if</span> (end &gt;= n - <span class="number">1</span>) <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> jumps;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>移掉k位数字</strong></p><p>给你一个以字符串表示的非负整数 <code>num</code> 和一个整数 <code>k</code> ，移除这个数中的 <code>k</code> 位数字，使得剩下的数字最小。请你以字符串形式返回这个最小的数字。</p><p><strong>让越小的数字尽可能排在越左边（高位）</strong>。这本质上是一个贪心问题，而实现这个贪心策略的最佳工具是 <strong>单调栈 (Monotonic Stack)</strong>。想象一个数字序列，如果左边的数字比右边的大（例如 <code>...43...</code>），那么删掉左边的这个“大数”，整个数字就会立刻变小。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">removeKdigits</span><span class="params">(string num, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (num.<span class="built_in">length</span>() == k) <span class="keyword">return</span> <span class="string">&quot;0&quot;</span>;</span><br><span class="line"></span><br><span class="line">        string res = <span class="string">&quot;&quot;</span>; <span class="comment">// 这里的 string 可以直接当做栈来用</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> d : num) &#123;</span><br><span class="line">            <span class="comment">// 当当前数字比“栈”顶小，且还有删除名额时，弹出末尾数字</span></span><br><span class="line">            <span class="keyword">while</span> (k &gt; <span class="number">0</span> &amp;&amp; !res.<span class="built_in">empty</span>() &amp;&amp; res.<span class="built_in">back</span>() &gt; d) &#123;</span><br><span class="line">                res.<span class="built_in">pop_back</span>();</span><br><span class="line">                k--;</span><br><span class="line">            &#125;</span><br><span class="line">            res.<span class="built_in">push_back</span>(d);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果 k 还没用完，从末尾截断（因为此时 res 已经是升序的了）</span></span><br><span class="line">        <span class="keyword">while</span> (k &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            res.<span class="built_in">pop_back</span>();</span><br><span class="line">            k--;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 处理前导零</span></span><br><span class="line">        <span class="type">int</span> start = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (start &lt; res.<span class="built_in">size</span>() &amp;&amp; res[start] == <span class="string">&#x27;0&#x27;</span>) &#123;</span><br><span class="line">            start++;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        string ans = res.<span class="built_in">substr</span>(start);</span><br><span class="line">        <span class="keyword">return</span> ans.<span class="built_in">empty</span>() ? <span class="string">&quot;0&quot;</span> : ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>根据身高重建队列</strong></p><p>假设有打乱顺序的一群人站成一个队列，数组 <code>people</code> 表示队列中一些人的属性（不一定按顺序）。每个 <code>people[i] = [hi, ki]</code> 表示第 <code>i</code> 个人的身高为 <code>hi</code> ，前面 <strong>正好</strong> 有 <code>ki</code> 个身高大于或等于 <code>hi</code> 的人。</p><p>请你重新构造并返回输入数组 <code>people</code> 所表示的队列。返回的队列应该格式化为数组 <code>queue</code> ，其中 <code>queue[j] = [hj, kj]</code> 是队列中第 <code>j</code> 个人的属性（<code>queue[0]</code> 是排在队列前面的人）。</p><p><strong>先安置“高个子”，再让“矮个子”插队</strong>。因为高个子的相对位置只受比他更高或一样高的人影响，而矮个子对他来说是“隐形”的。</p><p>我们可以通过两个步骤来搞定：</p><p>第一步：排序</p><p>我们要对数组进行排序：</p><ol><li><strong>身高 $h$ 降序</strong>：高个子排在前面。</li><li><strong>人数 $k$ 升序</strong>：如果身高一样，要求前面人少的排在前面。</li></ol><p>第二步：插入</p><p>遍历排序后的数组，直接将每个人插入到他对应的 $k$ 位置。</p><ul><li><strong>逻辑保证</strong>：当我们处理第 $i$ 个人时，已经在队列里的所有人身高都 $\ge$ 他的身高。</li><li>此时，他要求前面有 $k$ 个比他高或相等的人，我们就直接把他放在索引 $k$ 的位置。哪怕后面有更矮的人插到他前面，也不会影响他的 $k$ 值。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">reconstructQueue</span>(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; people) &#123;</span><br><span class="line">        <span class="comment">// 先排序身高</span></span><br><span class="line">        <span class="keyword">auto</span> comp = [](vector&lt;<span class="type">int</span>&gt;&amp; a,vector&lt;<span class="type">int</span>&gt;&amp; b) &#123;</span><br><span class="line">            <span class="keyword">if</span>(a[<span class="number">0</span>] == b[<span class="number">0</span>]) &#123;</span><br><span class="line">                <span class="comment">// 身高相等 排序低的在前面</span></span><br><span class="line">                <span class="keyword">return</span> a[<span class="number">1</span>]&lt;b[<span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> a[<span class="number">0</span>]&gt;b[<span class="number">0</span>];</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="built_in">sort</span>(people.<span class="built_in">begin</span>(),people.<span class="built_in">end</span>(),comp);</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        <span class="comment">//</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; p:people) &#123;</span><br><span class="line">           res.<span class="built_in">insert</span>(res.<span class="built_in">begin</span>()+p[<span class="number">1</span>],p);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>用最少数量的箭引爆气球</strong></p><p>有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 <code>points</code> ，其中<code>points[i] = [xstart, xend]</code> 表示水平直径在 <code>xstart</code> 和 <code>xend</code>之间的气球。你不知道气球的确切 y 坐标。</p><p>一支弓箭可以沿着 x 轴从不同点 <strong>完全垂直</strong> 地射出。在坐标 <code>x</code> 处射出一支箭，若有一个气球的直径的开始和结束坐标为 <code>xstart</code>，<code>xend</code>， 且满足  <code>xstart ≤ x ≤ xend</code>，则该气球会被 <strong>引爆</strong> 。可以射出的弓箭的数量 <strong>没有限制</strong> 。 弓箭一旦被射出之后，可以无限地前进。</p><p>给你一个数组 <code>points</code> ，<em>返回引爆所有气球所必须射出的 <strong>最小</strong> 弓箭数</em> 。</p><p><strong>按照气球的结束坐标 ($x_{end}$) 进行升序排序。</strong></p><p><strong>为什么选结束坐标？</strong>如果我们按照结束坐标排序，第一支箭的最优射出位置一定是<strong>第一个气球的结束位置</strong>。因为这样射出的箭，在引爆当前气球的同时，最有机会引爆后面那些“开始得很早”的气球。</p><p>算法步骤：</p><ol><li><strong>排序</strong>：将气球按 $x_{end}$ 从小到大排序。</li><li><strong>初始化</strong>：至少需要 1 支箭（假设数组不为空），初始射箭位置设为第一个气球的结束坐标。</li><li><strong>遍历</strong>：从第二个气球开始遍历：<ul><li>如果当前气球的开始坐标 $x_{start}$ <strong>大于</strong> 上一次射箭的位置：说明这支箭射不到这个气球。</li><li><strong>更新</strong>：我们需要一支新箭，增加箭的计数，并将射箭位置更新为当前气球的结束坐标。</li><li>否则：这支箭可以顺便引爆当前气球，位置保持不变。</li></ul></li></ol><p><strong>按区间的开始位置（Start）进行升序排序。</strong></p><p>核心思路：贪心加排序为什么要按开始位置排序？ 因为排序后，<strong>可以合并的区间一定是连续出现的</strong>。我们只需要比较当前区间的“开始”和上一个合并区间的“结束”即可。</p><p>算法步骤：</p><ol><li><strong>排序</strong>：按照每个区间的 <code>start</code> 从小到大排序。</li><li><strong>初始化</strong>：创建一个空的结果数组 <code>res</code>，先把第一个区间放进去。</li><li><strong>遍历</strong>：从第二个区间开始遍历：<ul><li>取 <code>res</code> 中最后一个区间的末尾 <code>last_end</code>。</li><li>取当前遍历区间的开头 <code>curr_start</code> 和末尾 <code>curr_end</code>。</li><li><strong>判断重叠</strong>：如果 <code>curr_start &lt;= last_end</code>，说明重叠了！<ul><li><strong>合并</strong>：将 <code>res</code> 中最后一个区间的末尾更新为 <code>max(last_end, curr_end)</code>。</li></ul></li><li><strong>不重叠</strong>：如果 <code>curr_start &gt; last_end</code>，说明这两个区间接不上。<ul><li><strong>直接添加</strong>：把当前区间整个塞进 <code>res</code>。</li></ul></li></ul></li></ol><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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findMinArrowShots</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; points)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (points.<span class="built_in">empty</span>()) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 按结束坐标升序排序</span></span><br><span class="line">        <span class="comment">// 注意：使用 lambda 表达式时，直接相减可能会导致溢出，建议使用 &lt; 判断</span></span><br><span class="line">        <span class="built_in">sort</span>(points.<span class="built_in">begin</span>(), points.<span class="built_in">end</span>(), [](<span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp; a, <span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp; b) &#123;</span><br><span class="line">            <span class="keyword">return</span> a[<span class="number">1</span>] &lt; b[<span class="number">1</span>];</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> arrows = <span class="number">1</span>; <span class="comment">// 至少需要一支箭</span></span><br><span class="line">        <span class="type">int</span> last_end = points[<span class="number">0</span>][<span class="number">1</span>]; <span class="comment">// 第一支箭射在第一个气球的末尾</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; points.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">            <span class="comment">// 2. 如果当前气球的开始位置在箭的射程之外</span></span><br><span class="line">            <span class="keyword">if</span> (points[i][<span class="number">0</span>] &gt; last_end) &#123;</span><br><span class="line">                arrows++; <span class="comment">// 必须再射一支</span></span><br><span class="line">                last_end = points[i][<span class="number">1</span>]; <span class="comment">// 更新这支箭的位置到当前气球末尾</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> arrows;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>合并区间</strong></p><p>以数组 <code>intervals</code> 表示若干个区间的集合，其中单个区间为 <code>intervals[i] = [starti, endi]</code> 。请你合并所有重叠的区间，并返回 <em>一个不重叠的区间数组，该数组需恰好覆盖输入中的所有区间</em> 。</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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">merge</span>(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; intervals) &#123;</span><br><span class="line">        <span class="keyword">auto</span> comp = [](vector&lt;<span class="type">int</span>&gt;&amp; a,vector&lt;<span class="type">int</span>&gt;&amp;b) &#123;</span><br><span class="line">            <span class="keyword">if</span>(a[<span class="number">0</span>] == b[<span class="number">0</span>]) &#123;</span><br><span class="line">                <span class="keyword">return</span> a[<span class="number">1</span>]&lt;b[<span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> a[<span class="number">0</span>]&lt;b[<span class="number">0</span>];</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="built_in">sort</span>(intervals.<span class="built_in">begin</span>(),intervals.<span class="built_in">end</span>(),comp);</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        res.<span class="built_in">push_back</span>(intervals[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;intervals.<span class="built_in">size</span>();i++) &#123;</span><br><span class="line">            <span class="keyword">auto</span>&amp; lastInterval = res.<span class="built_in">back</span>();</span><br><span class="line">            <span class="keyword">if</span>(lastInterval[<span class="number">1</span>]&gt;=intervals[i][<span class="number">0</span>]) &#123;</span><br><span class="line">                <span class="comment">// 重叠区间 合并</span></span><br><span class="line">                lastInterval[<span class="number">1</span>] = <span class="built_in">max</span>(lastInterval[<span class="number">1</span>],intervals[i][<span class="number">1</span>]);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="comment">// 不重叠区间</span></span><br><span class="line">                res.<span class="built_in">push_back</span>(intervals[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>合并区间（本题）</strong>：我们需要知道谁先开始，因为合并是向前推进的。如果按 End 排序，一个跨度极大的区间（如 <code>[1, 100]</code>）可能会排在最后，导致你之前合并好的所有小片段都要重新去和它比对，逻辑会变复杂。</p><p><strong>射气球/不重叠区间</strong>：我们需要尽早结束当前区间，好给后面的区间留位置。所以“谁先结束”是最重要的贪心标准。</p><p><strong>插入区间</strong></p><p>给你一个 <strong>无重叠的</strong> <em>，</em>按照区间起始端点排序的区间列表 <code>intervals</code>，其中 <code>intervals[i] = [starti, endi]</code> 表示第 <code>i</code> 个区间的开始和结束，并且 <code>intervals</code> 按照 <code>starti</code> 升序排列。同样给定一个区间 <code>newInterval = [start, end]</code> 表示另一个区间的开始和结束。</p><p>在 <code>intervals</code> 中插入区间 <code>newInterval</code>，使得 <code>intervals</code> 依然按照 <code>starti</code> 升序排列，且区间之间不重叠（如果有必要的话，可以合并区间）。</p><p>返回插入之后的 <code>intervals</code>。<strong>注意</strong> 你不需要原地修改 <code>intervals</code>。你可以创建一个新数组然后返回它。</p><p>可以把处理过程想象成在一条时间轴上排队，把 <code>newInterval</code> 插入进去，并把受到波及的人“融合”掉：</p><ul><li><p><strong>阶段一：左侧无重叠</strong></p><p>直接把所有<strong>结束时间</strong>早于 <code>newInterval</code> <strong>开始时间</strong>的区间丢进结果集。它们完全在左边，互不干扰。</p></li><li><p><strong>阶段二：中间重叠区（融合）</strong></p><p>只要当前的区间<strong>没有完全在 <code>newInterval</code> 的右边</strong>（即当前区间的开始时间 $\le$ <code>newInterval</code> 的结束时间），就说明有重叠。</p><ul><li><strong>融合策略</strong>：更新 <code>newInterval</code> 的起点为 <code>min(起点)</code>，终点为 <code>max(终点)</code>。</li></ul></li><li><p><strong>阶段三：右侧无重叠</strong></p><p>把合并后的 <code>newInterval</code> 放进结果集，然后把剩下的还没处理的区间（它们都在右边，开始时间晚于 <code>newInterval</code> 的结束时间）全部接在后面。</p></li></ul><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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">insert</span>(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; intervals,</span><br><span class="line">                               vector&lt;<span class="type">int</span>&gt;&amp; newInterval) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        <span class="type">int</span> i&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (i &lt; intervals.<span class="built_in">size</span>() &amp;&amp; intervals[i][<span class="number">1</span>] &lt; newInterval[<span class="number">0</span>]) &#123;</span><br><span class="line">            <span class="comment">// 不重叠 添加</span></span><br><span class="line">            res.<span class="built_in">push_back</span>(intervals[i]);</span><br><span class="line">            i++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (i &lt; intervals.<span class="built_in">size</span>() &amp;&amp; intervals[i][<span class="number">0</span>] &lt;= newInterval[<span class="number">1</span>]) &#123;</span><br><span class="line">            <span class="comment">// 重叠 合并</span></span><br><span class="line">            newInterval[<span class="number">0</span>] = <span class="built_in">min</span>(newInterval[<span class="number">0</span>], intervals[i][<span class="number">0</span>]);</span><br><span class="line">            newInterval[<span class="number">1</span>] = <span class="built_in">max</span>(newInterval[<span class="number">1</span>], intervals[i][<span class="number">1</span>]);</span><br><span class="line">            i++;</span><br><span class="line">        &#125;</span><br><span class="line">        res.<span class="built_in">push_back</span>(newInterval);</span><br><span class="line">        <span class="keyword">while</span> (i &lt; intervals.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(intervals[i]);</span><br><span class="line">            i++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>灌溉花园的最少水龙头数目</strong></p><p>在 x 轴上有一个一维的花园。花园长度为 <code>n</code>，从点 <code>0</code> 开始，到点 <code>n</code> 结束。</p><p>花园里总共有 <code>n + 1</code> 个水龙头，分别位于 <code>[0, 1, ..., n]</code> 。</p><p>给你一个整数 <code>n</code> 和一个长度为 <code>n + 1</code> 的整数数组 <code>ranges</code> ，其中 <code>ranges[i]</code> （下标从 0 开始）表示：如果打开点 <code>i</code> 处的水龙头，可以灌溉的区域为 <code>[i - ranges[i], i + ranges[i]]</code> 。</p><p>请你返回可以灌溉整个花园的 <strong>最少水龙头数目</strong> 。如果花园始终存在无法灌溉到的地方，请你返回 <strong>-1</strong> 。</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><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><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minTaps</span><span class="params">(<span class="type">int</span> n, vector&lt;<span class="type">int</span>&gt;&amp; ranges)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 预处理：将区间转换为类似于“跳跃游戏”的数组</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">max_reach</span><span class="params">(n + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="type">int</span> left = <span class="built_in">max</span>(<span class="number">0</span>, i - ranges[i]);</span><br><span class="line">            <span class="type">int</span> right = <span class="built_in">min</span>(n, i + ranges[i]);</span><br><span class="line">            <span class="comment">// 在 left 这个位置，最远能覆盖到 right</span></span><br><span class="line">            max_reach[left] = <span class="built_in">max</span>(max_reach[left], right);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 执行“跳跃游戏 II”的贪心逻辑</span></span><br><span class="line">        <span class="type">int</span> taps = <span class="number">0</span>;       <span class="comment">// 使用的水龙头数量</span></span><br><span class="line">        <span class="type">int</span> cur_end = <span class="number">0</span>;    <span class="comment">// 当前已覆盖范围的右边界</span></span><br><span class="line">        <span class="type">int</span> farthest = <span class="number">0</span>;   <span class="comment">// 下一步能达到的最远位置</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;n; i++) &#123;</span><br><span class="line">            <span class="comment">// 更新当前能探测到的最远位置</span></span><br><span class="line">            farthest = <span class="built_in">max</span>(farthest, max_reach[i]);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 如果连当前的 i 都覆盖不到，说明中间有断层</span></span><br><span class="line">            <span class="keyword">if</span> (i &gt;= farthest) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 到达当前水龙头的覆盖极限，必须开启下一个</span></span><br><span class="line">            <span class="keyword">if</span> (i == cur_end) &#123;</span><br><span class="line">                taps++;</span><br><span class="line">                cur_end = farthest;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 如果已经覆盖到 n，可以提前退出</span></span><br><span class="line">                <span class="keyword">if</span> (cur_end &gt;= n) <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> cur_end &gt;= n ? taps : <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>总体而言贪心类型的题目难度级别大部分只有中等，对应的解题技巧主要包括3个步骤。</p><p>●　掌握常见的贪心策略，例如本章的内容或其他人总结的经典贪心思想。</p><p>●　不断练习，积累题目经验，补充贪心策略，开拓自己的眼界和认知。</p><p>●　面对一道新颖的贪心类型的题目，要敢于设想一个局部最优策略。寻找局部最优策略的过程就是试错的过程，可以基于自己的生活经验或解题经验，并对策略的可行性进行简单的推理论证。</p><h1 id="回溯"><a href="#回溯" class="headerlink" title="回溯"></a>回溯</h1><p>回溯法是一种复杂度很高的暴力搜索算法，实现简单且有固定模板，常被用于搜索排列组合问题的所有可行性解。不同于普通的暴力搜索，回溯法会在每一步判断状态是否合法，而不是等到状态全部生成后再进行确认。当某一步状态非法时，它将回退到上一步中正确的位置，然后继续搜索其他不同的状态。前进和后退是回溯法的关键动作，因此可以使用递归去模拟整个过程，即使用递归实现回溯法。</p><p><strong>组合总和</strong></p><p>给你一个 <strong>无重复元素</strong> 的整数数组 <code>candidates</code> 和一个目标整数 <code>target</code> ，找出 <code>candidates</code> 中可以使数字和为目标数 <code>target</code> 的 所有 <strong>不同组合</strong> ，并以列表形式返回。你可以按 <strong>任意顺序</strong> 返回这些组合。</p><p><code>candidates</code> 中的 <strong>同一个</strong> 数字可以 <strong>无限制重复被选取</strong> 。如果至少一个数字的被选数量不同，则两种组合是不同的。 对于给定的输入，保证和为 <code>target</code> 的不同组合数少于 <code>150</code> 个。</p><p>回溯法的本质是深度优先遍历，具体的实现方法是递归，因此需要定义一个递归函数来模拟整个搜索过程，即dfs()。不断向下递归的过程，也就是搜索前进的过程，那么在这个过程中需要注意哪些问题呢？这些问题与递归函数的内容及参数息息相关，值得我们关注理解。</p><p>（i）如何区别不同的递归？或者如何知道现在搜索到哪里了？每一层递归的函数内容是固定的，有所区别的只有参数信息，因此可以将参数信息作为区分的标记。通过获取当前递归的参数信息，也就能够认识到搜索的位置了。也可以称这些参数信息为递归携带的状态，模板中定义了3个状态，分别是idx、cur和path。其中，idx标记位置信息，例如idx=1可以表示搜索到数组的第1个数字，idx=2可以表示搜索到数组的第2个数字；cur和path实际上都是从出发点到当前位置的路径上的某个信息，需要根据题目的要求灵活定义。</p><p>（ii）递归如何结束？有几个结束出口？搜索的目标就是找到可行性解。通常情况下找到可行性解就应该结束搜索，但在一些特殊场景下，不同的解可能会重叠，例如找到解后继续搜索可能会得到新的解，此时就不能结束搜索。此外，当无法继续搜索时也应该结束搜索，例如依次遍历数组元素，如果递归过程中idx等于数组末尾的下标，则不能继续往下搜索，否则会发生程序错误。</p><p>（iii）递归过程中状态可能会互相影响，如何解决？这个问题可能不是很好理解。举个例子来说，假设当前可以向左前进，也可以向右前进，并且需要保存走过的路径。基于上述第一个问题，应该在递归中携带状态path来保存当前路径，并在进入下一层递归之前改变状态：向左前进则path.append(left)，向右前进则path.append(right)。如果先选择向左前进，path已经发生变化，包括向左的一些路径信息，再选择向右前进就会存在问题。一种简单的解决方案是：每次进入下一层递归时重新复制path，但复制path的时间复杂度为O(n)，时间开销太大，无法充分利用path。可以考虑另外一种解决方案：在递归结束的地方恢复原来path的状态，即在下一层递归前通过path.append(num)改变状态，并在递归结束时通过path.pop()恢复状态。</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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, vector&lt;<span class="type">int</span>&gt;&amp; r, vector&lt;<span class="type">int</span>&gt;&amp; nums,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int</span> idx, <span class="type">int</span> sum_val, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (idx == nums.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (sum_val == target) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(r);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = idx; i &lt; nums.<span class="built_in">size</span>() &amp;&amp; (nums[i] + sum_val &lt;= target); i++) &#123;</span><br><span class="line">            r.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            <span class="built_in">backtrace</span>(res, r, nums, i, sum_val + nums[i], target);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">combinationSum</span>(vector&lt;<span class="type">int</span>&gt;&amp; candidates, <span class="type">int</span> target) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="built_in">sort</span>(candidates.<span class="built_in">begin</span>(), candidates.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">backtrace</span>(res, r, candidates, <span class="number">0</span>, <span class="number">0</span>, target);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>组合总和II</strong></p><p>给定一个候选人编号的集合 <code>candidates</code> 和一个目标数 <code>target</code> ，找出 <code>candidates</code> 中所有可以使数字和为 <code>target</code> 的组合。<code>candidates</code> 中的每个数字在每个组合中只能使用 <strong>一次</strong> 。</p><p><strong>注意：</strong>解集不能包含重复的组合。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; result;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; path;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtracking</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; candidates, <span class="type">int</span> target, <span class="type">int</span> sum, <span class="type">int</span> startIndex, vector&lt;<span class="type">bool</span>&gt;&amp; used)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (sum == target) &#123;</span><br><span class="line">            result.<span class="built_in">push_back</span>(path);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = startIndex; i &lt; candidates.<span class="built_in">size</span>() &amp;&amp; sum + candidates[i] &lt;= target; i++) &#123;</span><br><span class="line">            <span class="comment">// used[i - 1] == true，说明同一树枝candidates[i - 1]使用过</span></span><br><span class="line">            <span class="comment">// used[i - 1] == false，说明同一树层candidates[i - 1]使用过</span></span><br><span class="line">            <span class="comment">// 要对同一树层使用过的元素进行跳过</span></span><br><span class="line">            <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; candidates[i] == candidates[i - <span class="number">1</span>] &amp;&amp; used[i - <span class="number">1</span>] == <span class="literal">false</span>) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            sum += candidates[i];</span><br><span class="line">            path.<span class="built_in">push_back</span>(candidates[i]);</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            <span class="built_in">backtracking</span>(candidates, target, sum, i + <span class="number">1</span>, used); <span class="comment">// 和39.组合总和的区别1，这里是i+1，每个数字在每个组合中只能使用一次</span></span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">            sum -= candidates[i];</span><br><span class="line">            path.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">combinationSum2</span>(vector&lt;<span class="type">int</span>&gt;&amp; candidates, <span class="type">int</span> target) &#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">used</span><span class="params">(candidates.size(), <span class="literal">false</span>)</span></span>;</span><br><span class="line">        path.<span class="built_in">clear</span>();</span><br><span class="line">        result.<span class="built_in">clear</span>();</span><br><span class="line">        <span class="comment">// 首先把给candidates排序，让其相同的元素都挨在一起。</span></span><br><span class="line">        <span class="built_in">sort</span>(candidates.<span class="built_in">begin</span>(), candidates.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">backtracking</span>(candidates, target, <span class="number">0</span>, <span class="number">0</span>, used);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>组合总和<strong>III</strong></p><p>找出所有相加之和为 <code>n</code> 的 <code>k</code> 个数的组合，且满足下列条件：</p><ul><li>只使用数字1到9</li><li>每个数字 <strong>最多使用一次</strong> </li></ul><p>返回 <em>所有可能的有效组合的列表</em> 。该列表不能包含相同的组合两次，组合可以以任何顺序返回。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, <span class="type">int</span> sum_val, <span class="type">int</span> idx,</span></span></span><br><span class="line"><span class="params"><span class="function">                   vector&lt;<span class="type">int</span>&gt;&amp; r, <span class="type">int</span> k, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (r.<span class="built_in">size</span>() == k) &#123;</span><br><span class="line">            <span class="keyword">if</span> (sum_val == n) &#123;</span><br><span class="line">                res.<span class="built_in">push_back</span>(r);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = idx; i &lt;= <span class="number">9</span> &amp;&amp; i + sum_val &lt;= n; i++) &#123;</span><br><span class="line">            r.<span class="built_in">push_back</span>(i);</span><br><span class="line">            <span class="built_in">backtrace</span>(res, sum_val + i, i + <span class="number">1</span>, r, k, n);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">combinationSum3</span>(<span class="type">int</span> k, <span class="type">int</span> n) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="built_in">backtrace</span>(res, <span class="number">0</span>, <span class="number">1</span>, r, k, n);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>子集</strong></p><p>给你一个整数数组 <code>nums</code> ，数组中的元素 <strong>互不相同</strong> 。返回该数组所有可能的子集（幂集）。解集 <strong>不能</strong> 包含重复的子集。你可以按 <strong>任意顺序</strong> 返回解集。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res,vector&lt;<span class="type">int</span>&gt;&amp; r,vector&lt;<span class="type">int</span>&gt;&amp; nums,<span class="type">int</span> idx)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 添加所有节点</span></span><br><span class="line">        res.<span class="built_in">push_back</span>(r);</span><br><span class="line">        <span class="comment">// 截至条件</span></span><br><span class="line">        <span class="comment">// if(idx == nums.size()) &#123;</span></span><br><span class="line">        <span class="comment">//     return;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = idx;i&lt;nums.<span class="built_in">size</span>();i++) &#123;</span><br><span class="line">            r.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            <span class="built_in">backtrace</span>(res,r,nums,i<span class="number">+1</span>);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">subsets</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="built_in">backtrace</span>(res,r,nums,<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>全排列</strong></p><p>给定一个不含重复数字的数组 <code>nums</code> ，返回其 <em>所有可能的全排列</em> 。你可以 <strong>按任意顺序</strong> 返回答案。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, vector&lt;<span class="type">int</span>&gt;&amp; r, vector&lt;<span class="type">int</span>&gt;&amp; nums,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int</span> idx, vector&lt;<span class="type">bool</span>&gt;&amp; used)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 截至条件</span></span><br><span class="line">        <span class="keyword">if</span> (r.<span class="built_in">size</span>() == nums.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(r);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="comment">// 如果使用过</span></span><br><span class="line">            <span class="keyword">if</span> (used[i]) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            r.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            <span class="built_in">backtrace</span>(res, r, nums, i + <span class="number">1</span>, used);</span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">permute</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">used</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="built_in">backtrace</span>(res, r, nums, <span class="number">0</span>, used);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</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><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="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrack</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, vector&lt;<span class="type">int</span>&gt;&amp; output, <span class="type">int</span> first, <span class="type">int</span> len)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 所有数都填完了</span></span><br><span class="line">        <span class="keyword">if</span> (first == len) &#123;</span><br><span class="line">            res.<span class="built_in">emplace_back</span>(output);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = first; i &lt; len; ++i) &#123;</span><br><span class="line">            <span class="comment">// 动态维护数组</span></span><br><span class="line">            <span class="built_in">swap</span>(output[i], output[first]);</span><br><span class="line">            <span class="comment">// 继续递归填下一个数</span></span><br><span class="line">            <span class="built_in">backtrack</span>(res, output, first + <span class="number">1</span>, len);</span><br><span class="line">            <span class="comment">// 撤销操作</span></span><br><span class="line">            <span class="built_in">swap</span>(output[i], output[first]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">permute</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt; &gt; res;</span><br><span class="line">        <span class="built_in">backtrack</span>(res, nums, <span class="number">0</span>, (<span class="type">int</span>)nums.<span class="built_in">size</span>());</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>解数独</strong></p><p>给定一个不含重复数字的数组 <code>nums</code> ，返回其 <em>所有可能的全排列</em> 。你可以 <strong>按任意顺序</strong> 返回答案。</p><p>编写一个程序，通过填充空格来解决数独问题。</p><p>数独的解法需 <strong>遵循如下规则</strong>：</p><ol><li>数字 <code>1-9</code> 在每一行只能出现一次。</li><li>数字 <code>1-9</code> 在每一列只能出现一次。</li><li>数字 <code>1-9</code> 在每一个以粗实线分隔的 <code>3x3</code> 宫内只能出现一次。（请参考示例图）</li></ol><p>数独部分空格内已填入了数字，空白格用 <code>&#39;.&#39;</code> 表示。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; grid, <span class="type">int</span> row, <span class="type">int</span> col, <span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 每行</span></span><br><span class="line">        <span class="type">int</span> m = grid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (grid[row][j] == <span class="string">&#x27;0&#x27;</span> + num) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 每列</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (grid[i][col] == <span class="string">&#x27;0&#x27;</span> + num) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 格子内部</span></span><br><span class="line">        <span class="type">int</span> start_i = row / <span class="number">3</span> * <span class="number">3</span>;</span><br><span class="line">        <span class="type">int</span> start_j = col / <span class="number">3</span> * <span class="number">3</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = start_i; i &lt; start_i + <span class="number">3</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = start_j; j &lt; start_j + <span class="number">3</span>; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="string">&#x27;0&#x27;</span> + num) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; board)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = board.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = board[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 截至条件</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (board[i][j] != <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> num = <span class="number">1</span>; num &lt;= <span class="number">9</span>; num++) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="built_in">isValid</span>(board, i, j, num)) &#123;</span><br><span class="line">                        board[i][j] = <span class="string">&#x27;0&#x27;</span> + num;</span><br><span class="line">                        <span class="keyword">if</span> (<span class="built_in">backtrace</span>(board)) &#123;</span><br><span class="line">                            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                        board[i][j] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">solveSudoku</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; board)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 回溯进行选择 然后判断是否合规</span></span><br><span class="line">        <span class="type">int</span> m = board.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = board[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="built_in">backtrace</span>(board);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>全排列II</strong></p><p>给定一个可包含重复数字的序列 <code>nums</code> ，<strong><em>按任意顺序</em></strong> 返回所有不重复的全排列。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, vector&lt;<span class="type">int</span>&gt;&amp; r, vector&lt;<span class="type">bool</span>&gt;&amp; used,</span></span></span><br><span class="line"><span class="params"><span class="function">                   vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (r.<span class="built_in">size</span>() == nums.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(r);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="comment">// 同一层如果使用过</span></span><br><span class="line">            <span class="keyword">if</span>(i&gt;<span class="number">0</span> &amp;&amp; (nums[i] == nums[i<span class="number">-1</span>]) &amp;&amp; !used[i<span class="number">-1</span>]) &#123;</span><br><span class="line">                <span class="comment">// </span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 同一树枝使用过</span></span><br><span class="line">            <span class="keyword">if</span> (used[i]) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            r.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            <span class="built_in">backtrace</span>(res, r, used, nums);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">permuteUnique</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">used</span><span class="params">(nums.size())</span></span>;</span><br><span class="line">        <span class="built_in">sort</span>(nums.<span class="built_in">begin</span>(),nums.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">backtrace</span>(res, r, used, nums);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>N皇后</strong></p><p>按照国际象棋的规则，皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。</p><p><strong>n 皇后问题</strong> 研究的是如何将 <code>n</code> 个皇后放置在 <code>n×n</code> 的棋盘上，并且使皇后彼此之间不能相互攻击。</p><p>给你一个整数 <code>n</code> ，返回所有不同的 <strong>n 皇后问题</strong> 的解决方案。</p><p>每一种解法包含一个不同的 <strong>n 皇后问题</strong> 的棋子放置方案，该方案中 <code>&#39;Q&#39;</code> 和 <code>&#39;.&#39;</code> 分别代表了皇后和空位。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;string&gt;&amp; board, <span class="type">int</span> i, <span class="type">int</span> j)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 判断该位置能否放置</span></span><br><span class="line">        <span class="comment">// 同一行</span></span><br><span class="line">        <span class="type">int</span> n = board.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> col = <span class="number">0</span>; col &lt; n; col++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][col] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 同一列</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> row = <span class="number">0</span>; row &lt; n; row++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[row][j] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 斜线</span></span><br><span class="line">        <span class="comment">// 从右下到左上</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> row = i, col = j; row &gt;= <span class="number">0</span> &amp;&amp; col &gt;= <span class="number">0</span>; col--, row--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[row][col] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 从左下到右上</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> row = i, col = j; row &gt;= <span class="number">0</span> &amp;&amp; col &lt; n; col++, row--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[row][col] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;string&gt;&gt;&amp; res, vector&lt;string&gt;&amp; board,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int</span> row)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = board.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 结束条件 最后一行</span></span><br><span class="line">        <span class="keyword">if</span> (n == row) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(board);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 对于每一行</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(board, row, j)) &#123;</span><br><span class="line">                board[row][j] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">                <span class="built_in">backtrace</span>(res, board, row + <span class="number">1</span>);</span><br><span class="line">                board[row][j] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;string&gt;&gt; <span class="built_in">solveNQueens</span>(<span class="type">int</span> n) &#123;</span><br><span class="line">        vector&lt;vector&lt;string&gt;&gt; res;</span><br><span class="line">        <span class="function">string <span class="title">tmp</span><span class="params">(n, <span class="string">&#x27;.&#x27;</span>)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;string&gt; <span class="title">board</span><span class="params">(n, tmp)</span></span>;</span><br><span class="line">        <span class="built_in">backtrace</span>(res, board, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>组合</strong></p><p>给定两个整数 <code>n</code> 和 <code>k</code>，返回范围 <code>[1, n]</code> 中所有可能的 <code>k</code> 个数的组合。</p><p>你可以按 <strong>任何顺序</strong> 返回答案。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, <span class="type">int</span> n, vector&lt;<span class="type">int</span>&gt;&amp; r, <span class="type">int</span> idx,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int</span> cnt, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (cnt == k) &#123;</span><br><span class="line">            res.<span class="built_in">push_back</span>(r);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 剪枝</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = idx; cnt+n-i<span class="number">+1</span>&gt;=k; i++) &#123;</span><br><span class="line">            r.<span class="built_in">push_back</span>(i);</span><br><span class="line">            <span class="built_in">backtrace</span>(res, n, r, i + <span class="number">1</span>, cnt + <span class="number">1</span>, k);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">combine</span>(<span class="type">int</span> n, <span class="type">int</span> k) &#123;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="built_in">backtrace</span>(res, n, r, <span class="number">1</span>, <span class="number">0</span>, k);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>子集II</strong></p><p>给你一个整数数组 <code>nums</code> ，其中可能包含重复元素，请你返回该数组所有可能的 子集（幂集）。</p><p>解集 <strong>不能</strong> 包含重复的子集。返回的解集中，子集可以按 <strong>任意顺序</strong> 排列。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; res, vector&lt;<span class="type">int</span>&gt;&amp; r, vector&lt;<span class="type">int</span>&gt;&amp; nums,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int</span> idx, vector&lt;<span class="type">bool</span>&gt;&amp; used)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 添加每个节点</span></span><br><span class="line">        res.<span class="built_in">push_back</span>(r);</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = idx; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; nums[i] == nums[i - <span class="number">1</span>] &amp;&amp; used[i - <span class="number">1</span>] == <span class="literal">false</span>) &#123;</span><br><span class="line">                <span class="comment">// 同一层选择相同值</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            r.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">            <span class="built_in">backtrace</span>(res, r, nums, i + <span class="number">1</span>, used);</span><br><span class="line">            r.<span class="built_in">pop_back</span>();</span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">subsetsWithDup</span>(vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">used</span><span class="params">(sz)</span></span>;</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; res;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; r;</span><br><span class="line">        <span class="built_in">sort</span>(nums.<span class="built_in">begin</span>(),nums.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">backtrace</span>(res, r, nums, <span class="number">0</span>, used);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>黄金矿工</strong></p><p>你要开发一座金矿，地质勘测学家已经探明了这座金矿中的资源分布，并用大小为 <code>m * n</code> 的网格 <code>grid</code> 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量；如果该单元格是空的，那么就是 <code>0</code>。</p><p>为了使收益最大化，矿工需要按以下规则来开采黄金：</p><ul><li>每当矿工进入一个单元，就会收集该单元格中的所有黄金。</li><li>矿工每次可以从当前位置向上下左右四个方向走。</li><li>每个单元格只能被开采（进入）一次。</li><li><strong>不得开采</strong>（进入）黄金数目为 <code>0</code> 的单元格。</li><li>矿工可以从网格中 <strong>任意一个</strong> 有黄金的单元格出发或者是停止。</li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">    vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; dirs = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">backtrace</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid, <span class="type">int</span> x, <span class="type">int</span> y, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        res = <span class="built_in">max</span>(res, val);</span><br><span class="line">        <span class="comment">// 选择四个方向</span></span><br><span class="line">        <span class="type">int</span> m = grid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : dirs) &#123;</span><br><span class="line">            <span class="type">int</span> nx = x + dir.first;</span><br><span class="line">            <span class="type">int</span> ny = y + dir.second;</span><br><span class="line">            <span class="keyword">if</span> (nx &lt; <span class="number">0</span> || nx &gt;= m || ny &lt; <span class="number">0</span> || ny &gt;= n) &#123;</span><br><span class="line">                <span class="comment">// 无法到达</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (grid[nx][ny] == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 无法到达</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> tmp = grid[nx][ny];</span><br><span class="line">            grid[nx][ny] = <span class="number">0</span>;</span><br><span class="line">            <span class="built_in">backtrace</span>(grid, nx, ny, val + tmp);</span><br><span class="line">            grid[nx][ny] = tmp;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getMaximumGold</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; grid)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = grid.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = grid[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] != <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="type">int</span> val = grid[i][j];</span><br><span class="line">                    grid[i][j] = <span class="number">0</span>;</span><br><span class="line">                    <span class="built_in">backtrace</span>(grid, i, j, val);</span><br><span class="line">                    grid[i][j] = val;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>组合总和<strong>IV</strong></p><p>给你一个由 <strong>不同</strong> 整数组成的数组 <code>nums</code> ，和一个目标整数 <code>target</code> 。请你从 <code>nums</code> 中找出并返回总和为 <code>target</code> 的元素排列的个数。题目数据保证答案符合 32 位整数范围。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">combinationSum4</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// dp[i]表示总和为i的组合个数0</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">long</span> <span class="type">long</span>&gt; <span class="title">dp</span><span class="params">(target + <span class="number">1</span>,<span class="number">0</span>)</span></span>;</span><br><span class="line">        <span class="built_in">sort</span>(nums.<span class="built_in">begin</span>(), nums.<span class="built_in">end</span>());</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= target; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">auto</span> n : nums) &#123;</span><br><span class="line">                <span class="keyword">if</span> (n &lt;= i &amp;&amp; dp[i]+dp[i-n]&lt;=INT_MAX) &#123;</span><br><span class="line">                    <span class="comment">// 状态转移方程</span></span><br><span class="line">                    <span class="comment">// dp[i] += dp[i-n]</span></span><br><span class="line">                    dp[i] += dp[i - n];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[target];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>回溯算法能解决如下问题：</p><ul><li>组合问题：N个数里面按一定规则找出k个数的集合</li><li>排列问题：N个数按一定规则全排列，有几种排列方式</li><li>切割问题：一个字符串按一定规则有几种切割方式</li><li>子集问题：一个N个数的集合里有多少符合条件的子集</li><li>棋盘问题：N皇后，解数独等等</li></ul><h1 id="有趣的题目"><a href="#有趣的题目" class="headerlink" title="有趣的题目"></a>有趣的题目</h1><p><strong>多数元素</strong></p><p>给定一个大小为 <code>n</code> 的数组 <code>nums</code> ，返回其中的多数元素。多数元素是指在数组中出现次数 <strong>大于</strong> <code>⌊ n/2 ⌋</code> 的元素。你可以假设数组是非空的，并且给定的数组总是存在多数元素。</p><p>摩尔投票法，想象数组中每一个数字都是一个国家的士兵。因为题目保证存在一个“多数元素”（出现次数大于 $n/2$），这意味着这个国家的士兵人数比其他所有国家士兵加起来还要多。</p><p>算法规则：</p><ol><li><strong>阵地防守</strong>：我们选定一个候选人 <code>candidate</code>，并给他的血量 <code>count</code> 设为 1。</li><li><strong>遇到盟友</strong>：遍历到下一个数，如果它等于 <code>candidate</code>，血量 <code>count++</code>（阵地加固）。</li><li><strong>遇到敌人</strong>：如果它不等于 <code>candidate</code>，血量 <code>count--</code>（同归于尽）。</li><li><strong>阵地失守</strong>：如果 <code>count</code> 减到了 0，说明当前的候选人已经被耗尽了。我们立即更换当前遍历到的数字为新的 <code>candidate</code>，并将 <code>count</code> 重新设为 1。</li></ol><p><strong>结论</strong>：由于多数元素的人数超过了一半，即便它和其他所有数字“同归于尽”，最后剩下的那个 <code>candidate</code> 一定就是多数元素。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">majorityElement</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; n:nums) &#123;</span><br><span class="line">            <span class="keyword">if</span>(cnt == <span class="number">0</span>) &#123;</span><br><span class="line">                res = n;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(n == res) &#123;</span><br><span class="line">                cnt++;</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                cnt--;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>求众数II</strong></p><p>给定一个大小为n的数组，找出其中所有出现次数超过n/3 次的元素。说明：要求算法的时间复杂度为O(n)，空间复杂度为O(1)。</p><p>在处理超过 $n/2$ 的问题时，我们只需要 1 个候选人；现在我们要找超过 $n/3$ 的，就得维护 <strong>2 个候选人</strong> 和 <strong>2 个计数器</strong>。</p><p>算法规则：</p><ol><li><strong>初始化</strong>：选定两个不同的候选人 <code>cand1</code>, <code>cand2</code>，计数器 <code>cnt1</code>, <code>cnt2</code> 均为 0。</li><li><strong>遍历数组</strong>：<ul><li><strong>如果是老面孔</strong>：如果 <code>num == cand1</code>，则 <code>cnt1++</code>；如果 <code>num == cand2</code>，则 <code>cnt2++</code>。</li><li><strong>如果是新势力</strong>：如果此时 <code>cnt1 == 0</code>，则让当前数字当 <code>cand1</code>，并设 <code>cnt1 = 1</code>；同理处理 <code>cnt2 == 0</code> 的情况。</li><li><strong>三方同归于尽</strong>：如果当前数字既不是 <code>cand1</code> 也不是 <code>cand2</code>，且两个阵地都有人，那么当前数字与两个候选人<strong>同时各消耗 1 个血量</strong>（即 <code>cnt1--</code>, <code>cnt2--</code>）。</li></ul></li><li><strong>最终验证</strong>：由于题目不保证这两个候选人一定超过 $n/3$，所以最后必须再遍历一遍数组，核实这两个人的实际出现次数。</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">majorityElement</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> cand1 = <span class="number">0</span>, cand2 = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> cnt1 = <span class="number">0</span>, cnt2 = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 第一阶段：投票（三方混战）</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">            <span class="keyword">if</span> (num == cand1) &#123;</span><br><span class="line">                cnt1++;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (num == cand2) &#123;</span><br><span class="line">                cnt2++;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cnt1 == <span class="number">0</span>) &#123;</span><br><span class="line">                cand1 = num;</span><br><span class="line">                cnt1 = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cnt2 == <span class="number">0</span>) &#123;</span><br><span class="line">                cand2 = num;</span><br><span class="line">                cnt2 = <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                cnt1--;</span><br><span class="line">                cnt2--;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 第二阶段：验证（必不可少）</span></span><br><span class="line">        cnt1 = <span class="number">0</span>;</span><br><span class="line">        cnt2 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">            <span class="keyword">if</span> (num == cand1) cnt1++;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (num == cand2) cnt2++;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">        <span class="keyword">if</span> (cnt1 &gt; n / <span class="number">3</span>) res.<span class="built_in">push_back</span>(cand1);</span><br><span class="line">        <span class="keyword">if</span> (cnt2 &gt; n / <span class="number">3</span>) res.<span class="built_in">push_back</span>(cand2);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>检查一个数在数组中占绝大多数</strong></p><p>给出一个按 <strong>非递减</strong> 顺序排列的数组 <code>nums</code>，和一个目标数值 <code>target</code>。假如数组 <code>nums</code> 中绝大多数元素的数值都等于 <code>target</code>，则返回 <code>True</code>，否则请返回 <code>False</code>。所谓占绝大多数，是指在长度为 <code>N</code> 的数组中出现必须 <strong>超过 <code>N/2</code></strong> <strong>次</strong></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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lower_bound</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums,<span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 找到第一个大于等于target的元素的位置</span></span><br><span class="line">        <span class="type">int</span> left = <span class="number">0</span>,right = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">while</span>(left&lt;right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right-left)/<span class="number">2</span>+left;</span><br><span class="line">            <span class="keyword">if</span>(nums[mid]&gt;=target) &#123;</span><br><span class="line">                <span class="comment">// 值大于目标</span></span><br><span class="line">                right = mid;</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                left = mid<span class="number">+1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> right;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isMajorityElement</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 找到第一个元素 二分查找</span></span><br><span class="line">        <span class="type">int</span> idx = <span class="built_in">lower_bound</span>(nums,target);</span><br><span class="line">        <span class="type">int</span> lastPos = idx+nums.<span class="built_in">size</span>()/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(lastPos&lt;nums.<span class="built_in">size</span>() &amp;&amp; nums[lastPos] == target) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>柱状图中最大的矩形</strong></p><p>给定 <em>n</em> 个非负整数，用来表示柱状图中各个柱子的高度。每个柱子彼此相邻，且宽度为 1 。求在该柱状图中，能够勾勒出来的矩形的最大面积。</p><p>核心思路：寻找左右边界.对于任何一根柱子 $i$，如果以它的高度 $h_i$ 作为矩形的<strong>高度</strong>，那么这个矩形能向左右延伸多远呢？</p><ul><li><strong>左边界</strong>：左边第一个比 $h_i$ <strong>矮</strong>的柱子位置。</li><li><strong>右边界</strong>：右边第一个比 $h_i$ <strong>矮</strong>的柱子位置。</li></ul><p>如果我们能为每一根柱子快速找到这两个边界，面积就等于 $h_i \times (右边界 - 左边界 - 1)$。</p><p>为什么使用单调栈？单调栈能以 $O(n)$ 的时间复杂度找到所有元素的“左右第一个更小值”。</p><p>算法逻辑：</p><ol><li><strong>维护一个单调递增栈</strong>：栈中存放的是柱子的<strong>下标</strong>。</li><li><strong>遍历柱子</strong>：<ul><li>如果当前柱子高度 $\ge$ 栈顶柱子高度：入栈。</li><li>如果当前柱子高度 $&lt;$ 栈顶柱子高度：<ul><li>说明找到了栈顶柱子的<strong>右边界</strong>（即当前柱子）。</li><li>弹出栈顶元素，记录其高度为 $h$。</li><li>弹出后的新栈顶就是该柱子的<strong>左边界</strong>。</li><li>计算面积并更新最大值。</li></ul></li></ul></li><li><strong>技巧：哨兵节点</strong>：在原高度数组的前后各加一个高度为 <code>0</code> 的柱子。前面的 <code>0</code> 避免空栈判断，后面的 <code>0</code> 确保遍历结束时栈内所有元素都能被弹出计算。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">largestRectangleArea</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; heights)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 单调栈</span></span><br><span class="line">        stack&lt;<span class="type">int</span>&gt; stk;</span><br><span class="line">        <span class="comment">// 维持栈底到栈顶 从小到大</span></span><br><span class="line">        heights.<span class="built_in">insert</span>(heights.<span class="built_in">begin</span>(), <span class="number">0</span>);</span><br><span class="line">        heights.<span class="built_in">push_back</span>(<span class="number">0</span>);</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; heights.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">while</span> (!stk.<span class="built_in">empty</span>() &amp;&amp; heights[i] &lt; heights[stk.<span class="built_in">top</span>()]) &#123;</span><br><span class="line">                <span class="type">int</span> curHeight = heights[stk.<span class="built_in">top</span>()];</span><br><span class="line">                stk.<span class="built_in">pop</span>();</span><br><span class="line">                res = <span class="built_in">max</span>(res, curHeight * (i - <span class="number">1</span> - stk.<span class="built_in">top</span>()));</span><br><span class="line">            &#125;</span><br><span class="line">            stk.<span class="built_in">push</span>(i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>每日温度</strong></p><p><strong>下一个更大的元素I</strong></p><p><strong>接雨水</strong></p><p><strong>最大矩形</strong></p><p>给定一个仅包含 <code>0</code> 和 <code>1</code> 、大小为 <code>rows x cols</code> 的二维二进制矩阵，找出只包含 <code>1</code> 的最大矩形，并返回其面积。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maximalRectangle</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 每个值左侧连续为1的数目</span></span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">row</span>(m, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matrix[i][j] == <span class="string">&#x27;1&#x27;</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (j == <span class="number">0</span>) &#123;</span><br><span class="line">                        row[i][j] = <span class="number">1</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        row[i][j] = <span class="number">1</span> + row[i][j - <span class="number">1</span>];</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 单调栈 以行为一个单位 找到该单位左右/上下行 更小的对应的位置</span></span><br><span class="line">        <span class="comment">// 升序</span></span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            stack&lt;<span class="type">int</span>&gt; stk;</span><br><span class="line">            <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">left</span><span class="params">(m)</span></span>; <span class="comment">// up</span></span><br><span class="line">            <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">right</span><span class="params">(m,m)</span></span>;    <span class="comment">// down</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">                <span class="keyword">while</span> (!stk.<span class="built_in">empty</span>() &amp;&amp; row[stk.<span class="built_in">top</span>()][j] &gt; row[i][j]) &#123;</span><br><span class="line">                    <span class="comment">// 找到了stk.top右侧更小值</span></span><br><span class="line">                    right[stk.<span class="built_in">top</span>()] = i;</span><br><span class="line">                    stk.<span class="built_in">pop</span>();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (!stk.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">                    left[i] = stk.<span class="built_in">top</span>();</span><br><span class="line">                &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                    left[i] = <span class="number">-1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                stk.<span class="built_in">push</span>(i);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">                <span class="type">int</span> h = row[i][j];</span><br><span class="line">                res = <span class="built_in">max</span>(res, h * (right[i] - left[i] - <span class="number">1</span>));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>一周中的第几天</strong></p><p>给你一个日期，请你设计一个算法来判断它是对应一周中的哪一天。输入为三个整数：<code>day</code>、<code>month</code> 和 <code>year</code>，分别表示日、月、年。</p><p>您返回的结果必须是这几个值中的一个 <code>{&quot;Sunday&quot;, &quot;Monday&quot;, &quot;Tuesday&quot;, &quot;Wednesday&quot;, &quot;Thursday&quot;, &quot;Friday&quot;, &quot;Saturday&quot;}</code>。</p><p><strong>注意</strong>：1971 年 1 月 1 日是星期五。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 判断是否是闰年</span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isLeapYear</span><span class="params">(<span class="type">int</span> year)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (year % <span class="number">400</span> == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (year % <span class="number">4</span> == <span class="number">0</span> &amp;&amp; year % <span class="number">100</span> != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">string <span class="title">dayOfTheWeek</span><span class="params">(<span class="type">int</span> day, <span class="type">int</span> month, <span class="type">int</span> year)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 计算从1971/1/1到给定的时间隔了多少天</span></span><br><span class="line">        <span class="type">int</span> startYear = <span class="number">1971</span>;</span><br><span class="line">        <span class="type">int</span> leaps&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (startYear &lt; year) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isLeapYear</span>(startYear)) &#123;</span><br><span class="line">                leaps++;</span><br><span class="line">            &#125;</span><br><span class="line">            startYear++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> cnt = (year - <span class="number">1971</span>) * <span class="number">365</span> + leaps;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 处理当前年</span></span><br><span class="line">        <span class="comment">// 计算月份</span></span><br><span class="line">        <span class="type">int</span> daysOfMonth[] = &#123;<span class="number">0</span>, <span class="number">31</span>, <span class="number">28</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>, <span class="number">30</span>, <span class="number">31</span>&#125;;</span><br><span class="line">        <span class="type">int</span> start_month&#123;<span class="number">1</span>&#125;;</span><br><span class="line">        <span class="type">int</span> monthDays&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (start_month &lt; month) &#123;</span><br><span class="line">            <span class="keyword">if</span> (start_month == <span class="number">2</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="built_in">isLeapYear</span>(year)) &#123;</span><br><span class="line">                    monthDays++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            monthDays +=  daysOfMonth[start_month];</span><br><span class="line">            start_month++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> res = cnt + monthDays + day - <span class="number">1</span>;</span><br><span class="line">        string weekDay[] = &#123;</span><br><span class="line">            <span class="string">&quot;Friday&quot;</span>,  <span class="string">&quot;Saturday&quot;</span>,  <span class="string">&quot;Sunday&quot;</span>,   <span class="string">&quot;Monday&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Tuesday&quot;</span>, <span class="string">&quot;Wednesday&quot;</span>, <span class="string">&quot;Thursday&quot;</span>,</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="keyword">return</span> weekDay[res % <span class="number">7</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>水壶问题</strong></p><p>有两个水壶，容量分别为 <code>x</code> 和 <code>y</code> 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 <code>target</code> 升。</p><p>你可以：</p><ul><li>装满任意一个水壶</li><li>清空任意一个水壶</li><li>将水从一个水壶倒入另一个水壶，直到接水壶已满，或倒水壶已空。</li></ul><p>贝祖定理：简单来说，如果你有两个整数 $a$ 和 $b$，贝祖定理告诉你，你一定能找到另外两个整数 $x$ 和 $y$，使得它们的某种“组合”恰好等于它们的最大公约数。</p><p>对于不全为零的整数 $a$ 和 $b$，设 $d = \text{gcd}(a, b)$，则：</p><ol><li><p>必然存在整数 $x$ 和 $y$，使得：</p><script type="math/tex; mode=display">ax + by = d</script></li><li><p>更进一步，对于任何整数 $z$，$ax + by = z$ 有整数解的<strong>充分必要条件</strong>是：$z$ 是 $d$ 的倍数。</p></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">gcd</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>&#123; <span class="keyword">return</span> (y == <span class="number">0</span>) ? x : <span class="built_in">gcd</span>(y, x % y); &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">canMeasureWater</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y, <span class="type">int</span> target)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(target&gt;x+y) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> g = <span class="built_in">gcd</span>(x, y);</span><br><span class="line">        <span class="keyword">if</span> (target % g == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>虽然定理保证了 $x$ 和 $y$ 存在，但它没直接告诉你它们是谁。在实际操作中，我们使用<strong>扩展欧几里得算法（Extended Euclidean Algorithm）</strong>来反向推导。</p><p>核心步骤：</p><ol><li>先用普通的欧几里得算法（辗转相除法）算出 $d$。</li><li>将除法过程中的余数逐层反向代入，最终凑出 $ax + by = d$ 的形式。</li></ol><p>重要的推论：互质的情况，如果 $a$ 和 $b$ <strong>互质</strong>（即 $\text{gcd}(a, b) = 1$），那么：必然存在整数 $x$ 和 $y$，使得 $ax + by = 1$。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 扩展欧几里得算法</span></span><br><span class="line"><span class="comment"> * 返回值是 gcd(a, b)</span></span><br><span class="line"><span class="comment"> * x 和 y 会被更新为满足 ax + by = gcd(a, b) 的解</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">extended_gcd</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b, <span class="type">int</span> &amp;x, <span class="type">int</span> &amp;y)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (b == <span class="number">0</span>) &#123;</span><br><span class="line">        x = <span class="number">1</span>;</span><br><span class="line">        y = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> a;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> d = <span class="built_in">extended_gcd</span>(b, a % b, y, x);</span><br><span class="line">    y -= (a / b) * x;</span><br><span class="line">    <span class="keyword">return</span> d;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>可怜的小猪</strong></p><p>有<code>buckets</code> 桶液体，其中 <strong>正好有一桶</strong> 含有毒药，其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药，你可以喂一些猪喝，通过观察猪是否会死进行判断。不幸的是，你只有 <code>minutesToTest</code> 分钟时间来确定哪桶液体是有毒的。</p><p>喂猪的规则如下：</p><ol><li>选择若干活猪进行喂养</li><li>可以允许小猪同时饮用任意数量的桶中的水，并且该过程不需要时间。</li><li>小猪喝完水后，必须有 <code>minutesToDie</code> 分钟的冷却时间。在这段时间里，你只能观察，而不允许继续喂猪。</li><li>过了 <code>minutesToDie</code> 分钟后，所有喝到毒药的猪都会死去，其他所有猪都会活下来。</li><li>重复这一过程，直到时间用完。</li></ol><p>给你桶的数目 <code>buckets</code> ，<code>minutesToDie</code> 和 <code>minutesToTest</code> ，返回 <em>在规定时间内判断哪个桶有毒所需的 <strong>最小</strong> 猪数</em> 。</p><p>核心思路：猪的状态与实验轮数</p><p>首先，我们需要算出总共能进行多少轮实验。</p><p>设 $T = \lfloor \text{minutesToTest} / \text{minutesToDie} \rfloor$。</p><p>对于一头小猪，在整个实验结束时，它可能处于以下 <strong>$T + 1$ 种状态</strong>：</p><ol><li><p>在第 1 轮实验后死去。</p></li><li><p>在第 2 轮实验后死去。</p><p>…</p><p>T.  在第 $T$ 轮实验后死去。</p><p>T+1. 实验全部结束，它依然活着。</p></li></ol><p><strong>关键直觉</strong>：如果有 $p$ 头小猪，每头猪有 $T+1$ 种可能的状态，那么这 $p$ 头猪组合起来一共可以表示 $(T+1)^p$ 种不同的状态。</p><p>每一种“状态组合”都可以对应一桶水。为了能够从 <code>buckets</code> 桶水中定位出那一桶毒药，小猪们能表示的总状态数必须大于或等于桶数：</p><script type="math/tex; mode=display">(T + 1)^p \ge \text{buckets}</script><p>我们的目标是求最小的整数 $p$。对等式两边取对数：</p><script type="math/tex; mode=display">p \cdot \ln(T + 1) \ge \ln(\text{buckets})</script><script type="math/tex; mode=display">p \ge \frac{\ln(\text{buckets})}{\ln(T + 1)}</script><p>所以，$p = \lceil \log_{T+1}(\text{buckets}) \rceil$。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">poorPigs</span><span class="params">(<span class="type">int</span> buckets, <span class="type">int</span> minutesToDie, <span class="type">int</span> minutesToTest)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> rounds = minutesToTest / minutesToDie;</span><br><span class="line">        <span class="type">int</span> states = rounds + <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> pigs = <span class="number">0</span>;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> max_buckets = <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 使用循环累乘代替 log，确保整数运算的绝对准确</span></span><br><span class="line">        <span class="keyword">while</span> (max_buckets &lt; buckets) &#123;</span><br><span class="line">            max_buckets *= states;</span><br><span class="line">            pigs++;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> pigs;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="字符串模拟"><a href="#字符串模拟" class="headerlink" title="字符串模拟"></a>字符串模拟</h1><p><strong>字符串模拟</strong>。要攻克它们，我建议你按这个<strong>分级练习路径</strong>来：</p><p>第一关：入门级（固定映射）</p><ul><li><strong>LC 13. 罗马数字转整数</strong>：练习基本的哈希映射。</li><li><strong>LC 12. 整数转罗马数字</strong>：练习贪心凑数思想。</li></ul><p>第二关：进阶级（带有进制逻辑）</p><ul><li><strong>LC 168. Excel表列名称</strong>：理解 26 进制与偏移量的关系（这是你之前刚看过的）。</li><li><strong>LC 171. Excel表列序号</strong>：反向模拟。</li></ul><p>第三关：大模拟级（细节控）</p><ul><li><strong>LC 8. 字符串转换整数 (atoi)</strong>：练习处理空格、正负号、边界溢出（面试高频）。</li><li><strong>LC 43. 字符串相乘</strong>：练习手写竖式计算，理解大数处理逻辑。</li></ul><p>第四关：终极 Boss</p><ul><li><strong>LC 273. 整数转换英文表示</strong>：这就是你现在的目标。</li><li><strong>LC 68. 文本左右对齐</strong>：字符串模拟题的“噩梦”</li></ul><p><strong>整数转换英文表示</strong></p><p>将非负整数 <code>num</code> 转换为其对应的英文表示。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;string&gt; tens = &#123;<span class="string">&quot;&quot;</span>,      <span class="string">&quot;Ten&quot;</span>,   <span class="string">&quot;Twenty&quot;</span>,  <span class="string">&quot;Thirty&quot;</span>, <span class="string">&quot;Forty&quot;</span>,</span><br><span class="line">                           <span class="string">&quot;Fifty&quot;</span>, <span class="string">&quot;Sixty&quot;</span>, <span class="string">&quot;Seventy&quot;</span>, <span class="string">&quot;Eighty&quot;</span>, <span class="string">&quot;Ninety&quot;</span>&#125;;</span><br><span class="line">    vector&lt;string&gt; singles = &#123;<span class="string">&quot;&quot;</span>,        <span class="string">&quot;One&quot;</span>,       <span class="string">&quot;Two&quot;</span>,      <span class="string">&quot;Three&quot;</span>,</span><br><span class="line">                              <span class="string">&quot;Four&quot;</span>,    <span class="string">&quot;Five&quot;</span>,      <span class="string">&quot;Six&quot;</span>,      <span class="string">&quot;Seven&quot;</span>,</span><br><span class="line">                              <span class="string">&quot;Eight&quot;</span>,   <span class="string">&quot;Nine&quot;</span>,      <span class="string">&quot;Ten&quot;</span>,      <span class="string">&quot;Eleven&quot;</span>,</span><br><span class="line">                              <span class="string">&quot;Twelve&quot;</span>,  <span class="string">&quot;Thirteen&quot;</span>,  <span class="string">&quot;Fourteen&quot;</span>, <span class="string">&quot;Fifteen&quot;</span>,</span><br><span class="line">                              <span class="string">&quot;Sixteen&quot;</span>, <span class="string">&quot;Seventeen&quot;</span>, <span class="string">&quot;Eighteen&quot;</span>, <span class="string">&quot;Nineteen&quot;</span>&#125;;</span><br><span class="line">    vector&lt;string&gt; units = &#123;<span class="string">&quot;&quot;</span>, <span class="string">&quot;Thousand&quot;</span>, <span class="string">&quot;Million&quot;</span>, <span class="string">&quot;Billion&quot;</span>&#125;;</span><br><span class="line">    <span class="function">string <span class="title">helper</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 小于1000</span></span><br><span class="line">        <span class="keyword">if</span> (num == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (num &lt; <span class="number">20</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> singles[num] + <span class="string">&quot; &quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (num &lt; <span class="number">100</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> tens[num / <span class="number">10</span>] + <span class="string">&quot; &quot;</span> + <span class="built_in">helper</span>(num % <span class="number">10</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> singles[num / <span class="number">100</span>] + <span class="string">&quot; Hundred &quot;</span> + <span class="built_in">helper</span>(num % <span class="number">100</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">string <span class="title">numberToWords</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 每个三个一份</span></span><br><span class="line">        <span class="keyword">if</span> (num == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Zero&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        string res;</span><br><span class="line">        <span class="type">int</span> i&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (num) &#123;</span><br><span class="line">            <span class="keyword">if</span> (num % <span class="number">1000</span>) &#123;</span><br><span class="line">                <span class="comment">// 处理当前这三位，并在后面加上单位</span></span><br><span class="line">                res = <span class="built_in">helper</span>(num % <span class="number">1000</span>) + units[i] + <span class="string">&quot; &quot;</span> + res;</span><br><span class="line">            &#125;</span><br><span class="line">            num /= <span class="number">1000</span>;</span><br><span class="line">            i++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (!res.<span class="built_in">empty</span>() &amp;&amp; res.<span class="built_in">back</span>() == <span class="string">&#x27; &#x27;</span>) &#123;</span><br><span class="line">            res.<span class="built_in">pop_back</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>汉字数字转阿拉伯数字</strong></p><p>处理它的核心思路是：<strong>分权累加，遇“大单位”结算</strong>。</p><p><strong>核心逻辑：</strong></p><ol><li><p><strong>映射表</strong>：准备好数字（一:1）和单位（十:10, 百:100, 万:10000）。</p></li><li><p><strong>状态变量</strong>：</p><ul><li><code>section_sum</code>: 当前小节（万以下）的累加和。</li><li><code>total_sum</code>: 最终的总和。</li><li><code>temp_val</code>: 当前读到的数字。</li></ul></li><li><p><strong>遍历规则</strong>：</p><ul><li>读到<strong>数字</strong>：存入 <code>temp_val</code>。</li><li>读到<strong>小单位</strong>（十、百、千）：用 <code>temp_val</code> 乘以单位，加进 <code>section_sum</code>。</li><li>读到<strong>大单位</strong>（万、亿）：将 <code>section_sum</code> 加上 <code>temp_val</code> 后，整体乘以大单位，并入 <code>total_sum</code>，然后清空 <code>section_sum</code>。</li></ul><p>汉字数字的逻辑是<strong>“权重累加”</strong>。核心在于区分：</p><ul><li><strong>小单位</strong>（十、百、千）：仅修饰前一个数字。</li><li><strong>大单位</strong>（万、亿）：修饰前面整整一截数字。</li></ul></li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unordered_map&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">cnToAr</span><span class="params">(std::u16string s)</span> </span>&#123;</span><br><span class="line">    std::unordered_map&lt;<span class="type">char16_t</span>, <span class="type">int</span>&gt; digits = &#123;</span><br><span class="line">        &#123;<span class="string">u&#x27;零&#x27;</span>, <span class="number">0</span>&#125;, &#123;<span class="string">u&#x27;一&#x27;</span>, <span class="number">1</span>&#125;, &#123;<span class="string">u&#x27;二&#x27;</span>, <span class="number">2</span>&#125;, &#123;<span class="string">u&#x27;三&#x27;</span>, <span class="number">3</span>&#125;, &#123;<span class="string">u&#x27;四&#x27;</span>, <span class="number">4</span>&#125;,</span><br><span class="line">        &#123;<span class="string">u&#x27;五&#x27;</span>, <span class="number">5</span>&#125;, &#123;<span class="string">u&#x27;六&#x27;</span>, <span class="number">6</span>&#125;, &#123;<span class="string">u&#x27;七&#x27;</span>, <span class="number">7</span>&#125;, &#123;<span class="string">u&#x27;八&#x27;</span>, <span class="number">8</span>&#125;, &#123;<span class="string">u&#x27;九&#x27;</span>, <span class="number">9</span>&#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    std::unordered_map&lt;<span class="type">char16_t</span>, <span class="type">long</span> <span class="type">long</span>&gt; units = &#123;</span><br><span class="line">        &#123;<span class="string">u&#x27;十&#x27;</span>, <span class="number">10</span>&#125;, &#123;<span class="string">u&#x27;百&#x27;</span>, <span class="number">100</span>&#125;, &#123;<span class="string">u&#x27;千&#x27;</span>, <span class="number">1000</span>&#125;, </span><br><span class="line">        &#123;<span class="string">u&#x27;万&#x27;</span>, <span class="number">10000</span>&#125;, &#123;<span class="string">u&#x27;亿&#x27;</span>, <span class="number">100000000</span>&#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> total = <span class="number">0</span>;   <span class="comment">// 总结果</span></span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> section = <span class="number">0</span>; <span class="comment">// 当前“万”或“亿”之内的累加和</span></span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> num = <span class="number">0</span>;     <span class="comment">// 暂存当前读到的数字</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char16_t</span> ch : s) &#123;</span><br><span class="line">        <span class="keyword">if</span> (digits.<span class="built_in">count</span>(ch)) &#123;</span><br><span class="line">            num = digits[ch];</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (units.<span class="built_in">count</span>(ch)) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="type">long</span> unit = units[ch];</span><br><span class="line">            <span class="keyword">if</span> (unit == <span class="number">10000</span> || unit == <span class="number">100000000</span>) &#123;</span><br><span class="line">                <span class="comment">// 遇到大单位，结算当前 section 并乘以权重</span></span><br><span class="line">                section = (section + num) * unit;</span><br><span class="line">                total += section;</span><br><span class="line">                section = <span class="number">0</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 遇到小单位（十百千）</span></span><br><span class="line">                <span class="keyword">if</span> (num == <span class="number">0</span> &amp;&amp; unit == <span class="number">10</span>) num = <span class="number">1</span>; <span class="comment">// 处理“十二”开头的“十”</span></span><br><span class="line">                section += num * unit;</span><br><span class="line">            &#125;</span><br><span class="line">            num = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> total + section + num;</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unordered_map&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="comment">// 使用 static 成员，避免重复创建 map</span></span><br><span class="line">    <span class="keyword">inline</span> <span class="type">static</span> <span class="type">const</span> unordered_map&lt;<span class="type">char16_t</span>, <span class="type">long</span> <span class="type">long</span>&gt; vals = &#123;</span><br><span class="line">        &#123;<span class="string">u&#x27;零&#x27;</span>, <span class="number">0</span>&#125;, &#123;<span class="string">u&#x27;一&#x27;</span>, <span class="number">1</span>&#125;, &#123;<span class="string">u&#x27;二&#x27;</span>, <span class="number">2</span>&#125;, &#123;<span class="string">u&#x27;三&#x27;</span>, <span class="number">3</span>&#125;, &#123;<span class="string">u&#x27;四&#x27;</span>, <span class="number">4</span>&#125;,</span><br><span class="line">        &#123;<span class="string">u&#x27;五&#x27;</span>, <span class="number">5</span>&#125;, &#123;<span class="string">u&#x27;六&#x27;</span>, <span class="number">6</span>&#125;, &#123;<span class="string">u&#x27;七&#x27;</span>, <span class="number">7</span>&#125;, &#123;<span class="string">u&#x27;八&#x27;</span>, <span class="number">8</span>&#125;, &#123;<span class="string">u&#x27;九&#x27;</span>, <span class="number">9</span>&#125; <span class="comment">// 修正了“九”</span></span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">inline</span> <span class="type">static</span> <span class="type">const</span> unordered_map&lt;<span class="type">char16_t</span>, <span class="type">long</span> <span class="type">long</span>&gt; units = &#123;</span><br><span class="line">        &#123;<span class="string">u&#x27;十&#x27;</span>, <span class="number">10</span>&#125;, &#123;<span class="string">u&#x27;百&#x27;</span>, <span class="number">100</span>&#125;, &#123;<span class="string">u&#x27;千&#x27;</span>, <span class="number">1000</span>&#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">inline</span> <span class="type">static</span> <span class="type">const</span> unordered_map&lt;<span class="type">char16_t</span>, <span class="type">long</span> <span class="type">long</span>&gt; segments = &#123;</span><br><span class="line">        &#123;<span class="string">u&#x27;万&#x27;</span>, <span class="number">10000</span>&#125;, &#123;<span class="string">u&#x27;亿&#x27;</span>, <span class="number">100000000</span>&#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">chineseNumsToArabicNums</span><span class="params">(u16string s)</span> </span>&#123;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> res = <span class="number">0</span>;      <span class="comment">// 最终结果</span></span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> section = <span class="number">0</span>;  <span class="comment">// “万”或“亿”之内的段内和</span></span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> val = <span class="number">0</span>;      <span class="comment">// 当前数字</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char16_t</span> ch : s) &#123;</span><br><span class="line">            <span class="keyword">if</span> (vals.<span class="built_in">count</span>(ch)) &#123;</span><br><span class="line">                val = vals.<span class="built_in">at</span>(ch);</span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (units.<span class="built_in">count</span>(ch)) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="type">long</span> unit = units.<span class="built_in">at</span>(ch);</span><br><span class="line">                <span class="keyword">if</span> (val == <span class="number">0</span> &amp;&amp; unit == <span class="number">10</span>) val = <span class="number">1</span>; <span class="comment">// 处理“十二”</span></span><br><span class="line">                section += val * unit;</span><br><span class="line">                val = <span class="number">0</span>;</span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (segments.<span class="built_in">count</span>(ch)) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="type">long</span> segUnit = segments.<span class="built_in">at</span>(ch);</span><br><span class="line">                <span class="comment">// 核心修正：section 加上当前的 val，再乘以万或亿</span></span><br><span class="line">                section = (section + val) * segUnit;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 处理“亿”和“万”的嵌套（如一亿万，虽然少见但逻辑要通）</span></span><br><span class="line">                <span class="keyword">if</span> (segUnit == <span class="number">100000000</span>) &#123; </span><br><span class="line">                    res += section;</span><br><span class="line">                    section = <span class="number">0</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 如果是万，先存在 section 里，可能后面还有亿</span></span><br><span class="line">                &#125;</span><br><span class="line">                val = <span class="number">0</span>;</span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 处理零，通常不需要操作，只需重置 val 为 0 即可</span></span><br><span class="line">                val = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res + section + val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>阿拉伯数字转汉字数字</strong></p><p>反过来的核心逻辑是<strong>“四位一组”</strong>。中国数字是每 4 位（一个“万”）进一级，而不是英文的每 3 位。</p><p><strong>处理重点：</strong></p><ul><li><strong>零的处理</strong>：中间连续的 0 只读一个“零”（如 1005 $\rightarrow$ 一千零五）。</li><li><strong>末尾的零</strong>：每组末尾的 0 不读（如 120 $\rightarrow$ 一百二十）。</li></ul><p><strong>整数转罗马数字</strong></p><p>罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则：</p><ul><li>如果该值不是以 4 或 9 开头，请选择可以从输入中减去的最大值的符号，将该符号附加到结果，减去其值，然后将其余部分转换为罗马数字。</li><li>如果该值以 4 或 9 开头，使用 <strong>减法形式</strong>，表示从以下符号中减去一个符号，例如 4 是 5 (<code>V</code>) 减 1 (<code>I</code>): <code>IV</code> ，9 是 10 (<code>X</code>) 减 1 (<code>I</code>)：<code>IX</code>。仅使用以下减法形式：4 (<code>IV</code>)，9 (<code>IX</code>)，40 (<code>XL</code>)，90 (<code>XC</code>)，400 (<code>CD</code>) 和 900 (<code>CM</code>)。</li><li>只有 10 的次方（<code>I</code>, <code>X</code>, <code>C</code>, <code>M</code>）最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (<code>V</code>)，50 (<code>L</code>) 或 500 (<code>D</code>)。如果需要将符号附加4次，请使用 <strong>减法形式</strong>。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">intToRoman</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        vector&lt;pair&lt;<span class="type">int</span>, string&gt;&gt; vecs = &#123;</span><br><span class="line">            &#123;<span class="number">1000</span>, <span class="string">&quot;M&quot;</span>&#125;, &#123;<span class="number">900</span>, <span class="string">&quot;CM&quot;</span>&#125;, &#123;<span class="number">500</span>, <span class="string">&quot;D&quot;</span>&#125;, &#123;<span class="number">400</span>, <span class="string">&quot;CD&quot;</span>&#125;, &#123;<span class="number">100</span>, <span class="string">&quot;C&quot;</span>&#125;,</span><br><span class="line">            &#123;<span class="number">90</span>, <span class="string">&quot;XC&quot;</span>&#125;,  &#123;<span class="number">50</span>, <span class="string">&quot;L&quot;</span>&#125;,   &#123;<span class="number">40</span>, <span class="string">&quot;XL&quot;</span>&#125;, &#123;<span class="number">10</span>, <span class="string">&quot;X&quot;</span>&#125;,   &#123;<span class="number">9</span>, <span class="string">&quot;IX&quot;</span>&#125;,</span><br><span class="line">            &#123;<span class="number">5</span>, <span class="string">&quot;V&quot;</span>&#125;,    &#123;<span class="number">4</span>, <span class="string">&quot;IV&quot;</span>&#125;,   &#123;<span class="number">1</span>, <span class="string">&quot;I&quot;</span>&#125;&#125;;</span><br><span class="line">        string res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; p : vecs) &#123;</span><br><span class="line">            <span class="keyword">while</span> (num &gt;= p.first) &#123;</span><br><span class="line">                num -= p.first;</span><br><span class="line">                res += p.second;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (num == <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>罗马字转整数</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">romanToInt</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        unordered_map&lt;<span class="type">char</span>, <span class="type">int</span>&gt; umap = &#123;</span><br><span class="line">            &#123;<span class="string">&#x27;I&#x27;</span>, <span class="number">1</span>&#125;,   &#123;<span class="string">&#x27;V&#x27;</span>, <span class="number">5</span>&#125;,   &#123;<span class="string">&#x27;X&#x27;</span>, <span class="number">10</span>&#125;,  &#123;<span class="string">&#x27;L&#x27;</span>, <span class="number">50</span>&#125;,</span><br><span class="line">            &#123;<span class="string">&#x27;C&#x27;</span>, <span class="number">100</span>&#125;, &#123;<span class="string">&#x27;D&#x27;</span>, <span class="number">500</span>&#125;, &#123;<span class="string">&#x27;M&#x27;</span>, <span class="number">1000</span>&#125;,</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">// 字符右边比左边大就是减否则+</span></span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &lt; s.<span class="built_in">size</span>() - <span class="number">1</span> &amp;&amp; umap[s[i]] &lt; umap[s[i + <span class="number">1</span>]]) &#123;</span><br><span class="line">                res -= umap[s[i]];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                res += umap[s[i]];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>Excel表列名称</strong></p><p>给你一个整数 <code>columnNumber</code> ，返回它在 Excel 表中相对应的列名称。</p><p>例如：</p><figure class="highlight clean"><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">A -&gt; <span class="number">1</span></span><br><span class="line">B -&gt; <span class="number">2</span></span><br><span class="line">C -&gt; <span class="number">3</span></span><br><span class="line">...</span><br><span class="line">Z -&gt; <span class="number">26</span></span><br><span class="line">AA -&gt; <span class="number">27</span></span><br><span class="line">AB -&gt; <span class="number">28</span> </span><br><span class="line">...</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">convertToTitle</span><span class="params">(<span class="type">int</span> columnNumber)</span> </span>&#123;</span><br><span class="line">        string res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (columnNumber) &#123;</span><br><span class="line">            columnNumber--;</span><br><span class="line">            <span class="type">char</span> ch = (<span class="string">&#x27;A&#x27;</span> + (columnNumber % <span class="number">26</span>));</span><br><span class="line">            res = ch + res;</span><br><span class="line">            columnNumber /= <span class="number">26</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>表列序号</strong></p><p>给你一个字符串 <code>columnTitle</code> ，表示 Excel 表格中的列名称。返回 <em>该列名称对应的列序号</em> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">titleToNumber</span><span class="params">(string columnTitle)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : columnTitle) &#123;</span><br><span class="line">            res = res * <span class="number">26</span> + (ch - <span class="string">&#x27;A&#x27;</span> + <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>字符串转换整数</strong></p><p>请你来实现一个 <code>myAtoi(string s)</code> 函数，使其能将字符串转换成一个 32 位有符号整数。</p><p>函数 <code>myAtoi(string s)</code> 的算法如下：</p><ol><li><strong>空格：</strong>读入字符串并丢弃无用的前导空格（<code>&quot; &quot;</code>）</li><li><strong>符号：</strong>检查下一个字符（假设还未到字符末尾）为 <code>&#39;-&#39;</code> 还是 <code>&#39;+&#39;</code>。如果两者都不存在，则假定结果为正。</li><li><strong>转换：</strong>通过跳过前置零来读取该整数，直到遇到非数字字符或到达字符串的结尾。如果没有读取数字，则结果为0。</li><li><strong>舍入：</strong>如果整数数超过 32 位有符号整数范围 <code>[−231, 231 − 1]</code> ，需要截断这个整数，使其保持在这个范围内。具体来说，小于 <code>−231</code> 的整数应该被舍入为 <code>−231</code> ，大于 <code>231 − 1</code> 的整数应该被舍入为 <code>231 − 1</code> 。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">myAtoi</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> idx&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (idx &lt; s.<span class="built_in">size</span>()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s[idx] == <span class="string">&#x27; &#x27;</span>) &#123;</span><br><span class="line">                idx++;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">bool</span> neg&#123;&#125;;</span><br><span class="line">        <span class="keyword">if</span> (s[idx] == <span class="string">&#x27;-&#x27;</span>) &#123;</span><br><span class="line">            idx++;</span><br><span class="line">            neg = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (s[idx] == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">            idx++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> num&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = idx; i &lt; s.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s[i] &lt; <span class="string">&#x27;0&#x27;</span> || s[i] &gt; <span class="string">&#x27;9&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (num &gt; INT_MAX / <span class="number">10</span> ||</span><br><span class="line">                ((num == INT_MAX / <span class="number">10</span> )&amp;&amp;  (s[i]-<span class="string">&#x27;0&#x27;</span>) &gt; INT_MAX % <span class="number">10</span>)) &#123;</span><br><span class="line">                <span class="keyword">return</span> neg ? INT_MIN : INT_MAX;</span><br><span class="line">            &#125;</span><br><span class="line">            num = num * <span class="number">10</span> + (s[i] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> neg ? -num : num;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>为什么溢出判断是 <code>INT_MAX / 10</code>？</p><p>这是这道题最容易写错的地方。</p><ul><li><code>INT_MAX</code> 是 <strong>2147483647</strong>。</li><li>假设当前 <code>res</code> 是 <strong>214748364</strong>：<ul><li>如果你下一位 <code>digit</code> 是 <strong>8</strong>，那么 <code>214748364 * 10 + 8</code> 就会变成 <strong>2147483648</strong>，这已经超过了 <code>INT_MAX</code>。</li><li>所以，如果 <code>res &gt; 214748364</code>，或者 <code>res == 214748364</code> 且 <code>digit &gt; 7</code>，我们就直接根据正负号返回最大值或最小值。</li></ul></li></ul><p><strong>字符串相乘</strong></p><p>给定两个以字符串形式表示的非负整数 <code>num1</code> 和 <code>num2</code>，返回 <code>num1</code> 和 <code>num2</code> 的乘积，它们的乘积也表示为字符串形式。<strong>注意：</strong>不能使用任何内置的 BigInteger 库或直接将输入转换为整数。</p><p> 我们可以把乘法拆解为每一位的乘积。假设 <code>num1</code> 长度为 $m$，<code>num2</code> 长度为 $n$：</p><ol><li><p><strong>结果长度</strong>：$num1 \times num2$ 的积，其位数最多为 $m + n$（例如 $99 \times 99 = 9801$，$2+2=4$ 位）。</p></li><li><p><strong>下标映射（关键）</strong>：</p><p>如果我们从右往左遍历：</p><ul><li><code>num1[i]</code> 与 <code>num2[j]</code> 相乘的结果 <code>mul</code>。</li><li><code>mul</code> 的<strong>低位</strong>会累加到结果数组的下标 <code>i + j + 1</code>。</li><li><code>mul</code> 的<strong>高位（进位）</strong>会累加到结果数组的下标 <code>i + j</code>。</li></ul><p><img data-src="https://cdn.sekyoro.top/imgs/image-20260211173106800.png" alt="image-20260211173106800"></p></li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">multiply</span><span class="params">(string num1, string num2)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (num1 == <span class="string">&quot;0&quot;</span> || num2 == <span class="string">&quot;0&quot;</span>) <span class="keyword">return</span> <span class="string">&quot;0&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> m = num<span class="number">1.</span><span class="built_in">size</span>(), n = num<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 结果最多有 m + n 位</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">res</span><span class="params">(m + n, <span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 从后往前遍历进行乘法</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = m - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = n - <span class="number">1</span>; j &gt;= <span class="number">0</span>; j--) &#123;</span><br><span class="line">                <span class="comment">// 计算当前两位数字的乘积</span></span><br><span class="line">                <span class="type">int</span> mul = (num1[i] - <span class="string">&#x27;0&#x27;</span>) * (num2[j] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">                <span class="comment">// 加上原本由于之前的计算留在该位置上的进位</span></span><br><span class="line">                <span class="type">int</span> sum = mul + res[i + j + <span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 更新当前位和进位</span></span><br><span class="line">                res[i + j + <span class="number">1</span>] = sum % <span class="number">10</span>; <span class="comment">// 低位</span></span><br><span class="line">                res[i + j] += sum / <span class="number">10</span>;     <span class="comment">// 进位直接累加到前一位</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 转为字符串</span></span><br><span class="line">        string ans = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; res.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="comment">// 跳过前导零</span></span><br><span class="line">            <span class="keyword">if</span> (ans.<span class="built_in">empty</span>() &amp;&amp; res[i] == <span class="number">0</span>) <span class="keyword">continue</span>;</span><br><span class="line">            ans += <span class="built_in">to_string</span>(res[i]);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> ans.<span class="built_in">empty</span>() ? <span class="string">&quot;0&quot;</span> : ans;</span><br><span class="line">    &#125;</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">add</span><span class="params">(string num1, string num2)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> i = num<span class="number">1.</span><span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> j = num<span class="number">2.</span><span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">        string r;</span><br><span class="line">        <span class="type">int</span> car&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (i &gt;= <span class="number">0</span> || j &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">int</span> val = ((i &gt;= <span class="number">0</span>) ? (num1[i] - <span class="string">&#x27;0&#x27;</span>) : <span class="number">0</span>) +</span><br><span class="line">                      ((j &gt;= <span class="number">0</span>) ? (num2[j] - <span class="string">&#x27;0&#x27;</span>) : <span class="number">0</span>) + car;</span><br><span class="line">            car = val / <span class="number">10</span>;</span><br><span class="line">            r = <span class="built_in">to_string</span>(val % <span class="number">10</span>) + r;</span><br><span class="line">            i--;</span><br><span class="line">            j--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (car) &#123;</span><br><span class="line">            r = <span class="string">&quot;1&quot;</span> + r;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> r;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">string <span class="title">multiply</span><span class="params">(string num1, string num2)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (num1 == <span class="string">&quot;0&quot;</span>|| num2 == <span class="string">&quot;0&quot;</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;0&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        string res = <span class="string">&quot;0&quot;</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = num<span class="number">1.</span><span class="built_in">size</span>() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="type">char</span>&amp; ch1 = num1[i];</span><br><span class="line">            string r&#123;&#125;;</span><br><span class="line">            <span class="type">int</span> car&#123;&#125;;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = num<span class="number">2.</span><span class="built_in">size</span>() - <span class="number">1</span>; j &gt;= <span class="number">0</span>; j--) &#123;</span><br><span class="line">                <span class="type">char</span>&amp; ch2 = num2[j];</span><br><span class="line">                <span class="type">int</span> val = car + (ch1 - <span class="string">&#x27;0&#x27;</span>) * (ch2 - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">                car = val / <span class="number">10</span>;</span><br><span class="line">                val = val % <span class="number">10</span>;</span><br><span class="line">                r = <span class="built_in">to_string</span>(val) + r;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (car) &#123;</span><br><span class="line">                r = <span class="built_in">to_string</span>(car) + r;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> offset = num<span class="number">1.</span><span class="built_in">size</span>() - <span class="number">1</span> - i;</span><br><span class="line">            r += <span class="built_in">string</span>(offset, <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">            res = <span class="built_in">add</span>(r, res);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>KMP</strong></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><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><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">getNext</span><span class="params">(<span class="type">int</span>* next, <span class="type">const</span> string&amp; s)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> j = <span class="number">-1</span>;</span><br><span class="line">        next[<span class="number">0</span>] = j;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i &lt; s.<span class="built_in">size</span>(); i++) &#123; <span class="comment">// 注意i从1开始</span></span><br><span class="line">            <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; s[i] != s[j + <span class="number">1</span>]) &#123; <span class="comment">// 前后缀不相同了</span></span><br><span class="line">                j = next[j]; <span class="comment">// 向前回退</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (s[i] == s[j + <span class="number">1</span>]) &#123; <span class="comment">// 找到相同的前后缀</span></span><br><span class="line">                j++;</span><br><span class="line">            &#125;</span><br><span class="line">            next[i] = j; <span class="comment">// 将j（前缀的长度）赋给next[i]</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">strStr</span><span class="params">(string haystack, string needle)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (needle.<span class="built_in">size</span>() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">next</span><span class="params">(needle.size())</span></span>;</span><br><span class="line"><span class="built_in">getNext</span>(&amp;next[<span class="number">0</span>], needle);</span><br><span class="line">        <span class="type">int</span> j = <span class="number">-1</span>; <span class="comment">// // 因为next数组里记录的起始位置为-1</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; haystack.<span class="built_in">size</span>(); i++) &#123; <span class="comment">// 注意i就从0开始</span></span><br><span class="line">            <span class="keyword">while</span>(j &gt;= <span class="number">0</span> &amp;&amp; haystack[i] != needle[j + <span class="number">1</span>]) &#123; <span class="comment">// 不匹配</span></span><br><span class="line">                j = next[j]; <span class="comment">// j 寻找之前匹配的位置</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (haystack[i] == needle[j + <span class="number">1</span>]) &#123; <span class="comment">// 匹配，j和i同时向后移动</span></span><br><span class="line">                j++; <span class="comment">// i的增加在for循环里</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (j == (needle.<span class="built_in">size</span>() - <span class="number">1</span>) ) &#123; <span class="comment">// 文本串s里出现了模式串t</span></span><br><span class="line">                <span class="keyword">return</span> (i - needle.<span class="built_in">size</span>() + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h1><p>合并有序链表</p><p>将两个升序链表合并为一个新的 <strong>升序</strong> 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 </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="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">mergeTwoLists</span><span class="params">(ListNode* list1, ListNode* list2)</span> </span>&#123;</span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        ListNode* cur = dummy;</span><br><span class="line">        <span class="keyword">while</span> (list1 &amp;&amp; list2) &#123;</span><br><span class="line">            <span class="keyword">if</span> (list1-&gt;val &lt; list2-&gt;val) &#123;</span><br><span class="line">                cur-&gt;next = list1;</span><br><span class="line">                list1 = list1-&gt;next;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                cur-&gt;next = list2;</span><br><span class="line">                list2 = list2-&gt;next;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (list1) &#123;</span><br><span class="line">            cur-&gt;next = list1;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (list2) &#123;</span><br><span class="line">            cur-&gt;next = list2;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>排序链表</strong></p><p>给你链表的头结点 <code>head</code> ，请将其按 <strong>升序</strong> 排列并返回 <strong>排序后的链表</strong> 。</p><p>归并排序链表可以分为三个步骤：</p><ol><li><strong>切分（Split）</strong>：使用<strong>快慢指针</strong>找到链表的中点，将链表断开成左右两部分。</li><li><strong>递归（Recursive Sort）</strong>：递归地对左右两部分进行排序。</li><li><strong>合并（Merge）</strong>：将两个有序的小链表合并成一个大的有序链表（类似于“合并两个有序链表”）。</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">sortList</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 基准情况：如果链表为空或只有一个节点，直接返回</span></span><br><span class="line">        <span class="keyword">if</span> (!head || !head-&gt;next) <span class="keyword">return</span> head;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 找到中点并断开</span></span><br><span class="line">        ListNode* mid = <span class="built_in">getMid</span>(head);</span><br><span class="line">        ListNode* rightHead = mid-&gt;next;</span><br><span class="line">        mid-&gt;next = <span class="literal">nullptr</span>; <span class="comment">// 【关键】切断连接</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 递归排序左右两半</span></span><br><span class="line">        ListNode* left = <span class="built_in">sortList</span>(head);</span><br><span class="line">        ListNode* right = <span class="built_in">sortList</span>(rightHead);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 合并有序链表</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">merge</span>(left, right);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 快慢指针找中点（返回前半部分的最后一个节点）</span></span><br><span class="line">    <span class="function">ListNode* <span class="title">getMid</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode *slow = head, *fast = head-&gt;next;</span><br><span class="line">        <span class="keyword">while</span> (fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> slow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 合并两个有序链表</span></span><br><span class="line">    <span class="function">ListNode* <span class="title">merge</span><span class="params">(ListNode* l1, ListNode* l2)</span> </span>&#123;</span><br><span class="line">        <span class="function">ListNode <span class="title">dummy</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line">        ListNode* tail = &amp;dummy;</span><br><span class="line">        <span class="keyword">while</span> (l1 &amp;&amp; l2) &#123;</span><br><span class="line">            <span class="keyword">if</span> (l1-&gt;val &lt; l2-&gt;val) &#123;</span><br><span class="line">                tail-&gt;next = l1;</span><br><span class="line">                l1 = l1-&gt;next;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                tail-&gt;next = l2;</span><br><span class="line">                l2 = l2-&gt;next;</span><br><span class="line">            &#125;</span><br><span class="line">            tail = tail-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        tail-&gt;next = l1 ? l1 : l2;</span><br><span class="line">        <span class="keyword">return</span> dummy.next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>反转链表</strong></p><p>给你单链表的头节点 <code>head</code> ，请你反转链表，并返回反转后的链表。</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></pre></td><td class="code"><pre><span class="line"><span class="function">ListNode* <span class="title">reverseNode</span><span class="params">(ListNode* node, ListNode* prev = <span class="literal">nullptr</span>)</span> </span>&#123;</span><br><span class="line">     <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">         <span class="keyword">return</span> prev;</span><br><span class="line">     &#125;</span><br><span class="line">     ListNode* n = node-&gt;next;</span><br><span class="line">     node-&gt;next = prev;</span><br><span class="line">     <span class="keyword">return</span> <span class="built_in">reverseNode</span>(n, node);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>反转链表II</p><p>给你单链表的头指针 <code>head</code> 和两个整数 <code>left</code> 和 <code>right</code> ，其中 <code>left &lt;= right</code> 。请你反转从位置 <code>left</code> 到位置 <code>right</code> 的链表节点，返回 <strong>反转后的链表</strong> 。</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><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><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">       <span class="function">ListNode* <span class="title">reverseNode</span><span class="params">(ListNode* node, ListNode* prev = <span class="literal">nullptr</span>)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (node == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> prev;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* n = node-&gt;next;</span><br><span class="line">        node-&gt;next = prev;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">reverseNode</span>(n, node);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseBetween</span><span class="params">(ListNode* head, <span class="type">int</span> left, <span class="type">int</span> right)</span> </span>&#123;</span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode* cur = dummy;</span><br><span class="line">        left--;</span><br><span class="line">        <span class="keyword">while</span>(left--) &#123;</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 找到左边节点的前一个节点</span></span><br><span class="line">        <span class="comment">// 方便进行链表头反转</span></span><br><span class="line">        ListNode* pre = cur;</span><br><span class="line">        cur = dummy;</span><br><span class="line">        <span class="keyword">while</span>(right--) &#123;</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 找到右边节点</span></span><br><span class="line">        <span class="comment">// 方便找到下一个节点并设置nullptr</span></span><br><span class="line">        ListNode* rightNode = cur;</span><br><span class="line">        ListNode* tail = rightNode-&gt;next;</span><br><span class="line">        rightNode-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line">        ListNode* start = pre-&gt;next;</span><br><span class="line">        pre-&gt;next = <span class="built_in">reverseNode</span>(pre-&gt;next);</span><br><span class="line">        start-&gt;next = tail;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><img data-src="https://files.seeusercontent.com/2026/02/22/9fuG/image-20260222222516630.png" alt="image-20260222222516630"></p><p><a href="https://leetcode.cn/problems/remove-nth-node-from-end-of-list/">删除链表的倒数第 N 个结点</a></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">removeNthFromEnd</span><span class="params">(ListNode* head, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 双指针</span></span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode *slow = dummy, *fast = dummy;</span><br><span class="line">        <span class="keyword">while</span> (n--) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        slow-&gt;next = slow-&gt;next-&gt;next;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>回文链表</strong></p><p>给你一个单链表的头节点 <code>head</code> ，请你判断该链表是否为回文链表。如果是，返回 <code>true</code> ；否则，返回 <code>false</code> </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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">getPreMidNode</span><span class="params">(ListNode* node)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode *slow = node, *fast = node;</span><br><span class="line">        <span class="keyword">while</span> (fast-&gt;next &amp;&amp; fast-&gt;next-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> slow;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseNode</span><span class="params">(ListNode* node, ListNode* prev = <span class="literal">nullptr</span>)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) &#123;</span><br><span class="line">            <span class="keyword">return</span> prev;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* next_node = node-&gt;next;</span><br><span class="line">        node-&gt;next = prev;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">reverseNode</span>(next_node, node);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isPalindrome</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 中点</span></span><br><span class="line">        ListNode* preNode = <span class="built_in">getPreMidNode</span>(head);</span><br><span class="line">        ListNode* mid = preNode-&gt;next;</span><br><span class="line">        preNode-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line">        <span class="comment">// 反转</span></span><br><span class="line">        mid = <span class="built_in">reverseNode</span>(mid);</span><br><span class="line">        <span class="comment">// 对比</span></span><br><span class="line">        ListNode* cur = head;</span><br><span class="line">        <span class="keyword">while</span> (cur &amp;&amp; mid) &#123;</span><br><span class="line">            <span class="keyword">if</span> (cur-&gt;val != mid-&gt;val) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            cur = cur-&gt;next;</span><br><span class="line">            mid = mid-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>链表的中间节点</strong></p><p>给你单链表的头结点 <code>head</code> ，请你找出并返回链表的中间结点。如果有两个中间结点，则返回第二个中间结点</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">middleNode</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode* slow = head,*fast = head;</span><br><span class="line">        <span class="keyword">while</span>(fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> slow;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>删除链表的中间节点</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">deleteMiddle</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode *slow = dummy, *fast = head;</span><br><span class="line">        <span class="keyword">while</span> (fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        slow-&gt;next = slow-&gt;next-&gt;next;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>重排链表</strong></p><p>给定一个单链表 <code>L</code> 的头节点 <code>head</code> ，单链表 <code>L</code> 表示为：</p><figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">L0</span> → <span class="symbol">L1</span> → … → <span class="built_in">Ln</span> - <span class="number">1</span> → <span class="built_in">Ln</span></span><br></pre></td></tr></table></figure><p>请将其重新排列后变为：</p><figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">L0</span> → <span class="built_in">Ln</span> → <span class="symbol">L1</span> → <span class="built_in">Ln</span> - <span class="number">1</span> → <span class="symbol">L2</span> → <span class="built_in">Ln</span> - <span class="number">2</span> → …</span><br></pre></td></tr></table></figure><p>不能只是单纯的改变节点内部的值，而是需要实际的进行节点交换。</p><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">reorderList</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!head || !head-&gt;next || !head-&gt;next-&gt;next) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 使用快慢指针找到中点</span></span><br><span class="line">        ListNode *slow = head, *fast = head;</span><br><span class="line">        <span class="keyword">while</span> (fast-&gt;next &amp;&amp; fast-&gt;next-&gt;next) &#123;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 翻转后半部分链表 (从 slow-&gt;next 开始)</span></span><br><span class="line">        ListNode* mid = slow;</span><br><span class="line">        ListNode* l2 = mid-&gt;next;</span><br><span class="line">        mid-&gt;next = <span class="literal">nullptr</span>; <span class="comment">// 断开前半部分和后半部分</span></span><br><span class="line">        l2 = <span class="built_in">reverseList</span>(l2);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 交错合并两个链表 l1 和 l2</span></span><br><span class="line">        ListNode* l1 = head;</span><br><span class="line">        <span class="keyword">while</span> (l1 &amp;&amp; l2) &#123;</span><br><span class="line">            ListNode* next1 = l1-&gt;next;</span><br><span class="line">            ListNode* next2 = l2-&gt;next;</span><br><span class="line"></span><br><span class="line">            l1-&gt;next = l2;</span><br><span class="line">            l2-&gt;next = next1;</span><br><span class="line"></span><br><span class="line">            l1 = next1;</span><br><span class="line">            l2 = next2;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseList</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode* prev = <span class="literal">nullptr</span>;</span><br><span class="line">        ListNode* curr = head;</span><br><span class="line">        <span class="keyword">while</span> (curr) &#123;</span><br><span class="line">            ListNode* nextTemp = curr-&gt;next;</span><br><span class="line">            curr-&gt;next = prev;</span><br><span class="line">            prev = curr;</span><br><span class="line">            curr = nextTemp;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> prev;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>环形链表</strong></p><p>给你一个链表的头节点 <code>head</code> ，判断链表中是否有环。</p><p>如果链表中有某个节点，可以通过连续跟踪 <code>next</code> 指针再次到达，则链表中存在环。 为了表示给定链表中的环，评测系统内部使用整数 <code>pos</code> 来表示链表尾连接到链表中的位置（索引从 0 开始）。<strong>注意：<code>pos</code> 不作为参数进行传递</strong> 。仅仅是为了标识链表的实际情况。</p><p><em>如果链表中存在环</em> ，则返回 <code>true</code> 。 否则，返回 <code>false</code> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">hasCycle</span><span class="params">(ListNode *head)</span> </span>&#123;</span><br><span class="line">        ListNode* slow = head,*fast = head;</span><br><span class="line">        <span class="keyword">while</span>(fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            <span class="keyword">if</span>(slow == fast) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>环形链表<strong>II</strong> 给定一个链表，返回链表开始入环的第一个节点。 </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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode *<span class="title">detectCycle</span><span class="params">(ListNode *head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 先使用 </span></span><br><span class="line">        ListNode* slow= head,*fast = head;</span><br><span class="line">        <span class="keyword">while</span>(fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            <span class="keyword">if</span>(slow == fast) &#123;</span><br><span class="line">                <span class="comment">// 快慢指针相遇 快指针领先一圈</span></span><br><span class="line">                <span class="comment">// a+b+(b+c) = 2*(a+b)</span></span><br><span class="line">                <span class="comment">//    =&gt; a = c</span></span><br><span class="line">                ListNode* cur = head;</span><br><span class="line">                <span class="keyword">while</span>(cur!=slow) &#123;</span><br><span class="line">                    cur = cur-&gt;next;</span><br><span class="line">                    slow = slow-&gt;next;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> cur;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>相交链表</strong></p><p>给定两个单链表的头节点 <code>headA</code> 和 <code>headB</code> ，请找出并返回两个单链表相交的起始节点。如果两个链表没有交点，返回 <code>null</code> </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode *<span class="title">getIntersectionNode</span><span class="params">(ListNode *headA, ListNode *headB)</span> </span>&#123;</span><br><span class="line">        ListNode* cur1 = headA,*cur2 = headB;</span><br><span class="line">        <span class="keyword">while</span>(cur1!=cur2) &#123;</span><br><span class="line">            cur1 = (cur1 == <span class="literal">nullptr</span>)? headB:cur1-&gt;next;</span><br><span class="line">            cur2 = (cur2 == <span class="literal">nullptr</span>)? headA:cur2-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cur1;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>链表最大孪生和</strong></p><p>在一个大小为 <code>n</code> 且 <code>n</code> 为 <strong>偶数</strong> 的链表中，对于 <code>0 &lt;= i &lt;= (n / 2) - 1</code> 的 <code>i</code> ，第 <code>i</code> 个节点（下标从 <strong>0</strong> 开始）的孪生节点为第 <code>(n-1-i)</code> 个节点 。</p><ul><li>比方说，<code>n = 4</code> 那么节点 <code>0</code> 是节点 <code>3</code> 的孪生节点，节点 <code>1</code> 是节点 <code>2</code> 的孪生节点。这是长度为 <code>n = 4</code> 的链表中所有的孪生节点。</li></ul><p><strong>孪生和</strong> 定义为一个节点和它孪生节点两者值之和。</p><p>给你一个长度为偶数的链表的头节点 <code>head</code> ，请你返回链表的 <strong>最大孪生和</strong> 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">midNode</span><span class="params">(ListNode* node)</span> </span>&#123;</span><br><span class="line">        ListNode *slow = node, *fast = node;</span><br><span class="line">        <span class="keyword">while</span> (fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> slow;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">ListNode* <span class="title">revereNode</span><span class="params">(ListNode* node)</span> </span>&#123;</span><br><span class="line">        ListNode* prev&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span> (node) &#123;</span><br><span class="line">            ListNode* n = node-&gt;next;</span><br><span class="line">            node-&gt;next = prev;</span><br><span class="line">            prev = node;</span><br><span class="line">            node = n;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> prev;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">pairSum</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获取中间节点</span></span><br><span class="line">        ListNode* mid = <span class="built_in">midNode</span>(head);</span><br><span class="line">        ListNode* l=<span class="built_in">revereNode</span>(mid);</span><br><span class="line">        <span class="type">int</span> max_val&#123;INT_MIN&#125;;</span><br><span class="line">        <span class="keyword">while</span>(head &amp;&amp; l) &#123;</span><br><span class="line">            max_val = <span class="built_in">max</span>(max_val,head-&gt;val+l-&gt;val);</span><br><span class="line">            head = head-&gt;next;</span><br><span class="line">            l = l-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> max_val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a href="https://leetcode.cn/problems/reverse-nodes-in-k-group/">K 个一组翻转链表</a></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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseKGroup</span><span class="params">(ListNode* head, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建一个虚拟头节点，方便处理</span></span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> <span class="built_in">ListNode</span>(<span class="number">0</span>);</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        </span><br><span class="line">        ListNode* prev = dummy;   <span class="comment">// 上一组的尾节点</span></span><br><span class="line">        ListNode* end = dummy;    <span class="comment">// 当前组的尾节点</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (end-&gt;next != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            <span class="comment">// 找到当前组的尾节点</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; k &amp;&amp; end != <span class="literal">nullptr</span>; i++) &#123;</span><br><span class="line">                end = end-&gt;next;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 如果不足 k 个，直接结束</span></span><br><span class="line">            <span class="keyword">if</span> (end == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 记录当前组的头节点和下一组的头节点</span></span><br><span class="line">            ListNode* start = prev-&gt;next;</span><br><span class="line">            ListNode* nextGroup = end-&gt;next;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 断开当前组与后面的连接</span></span><br><span class="line">            end-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 翻转当前组</span></span><br><span class="line">            prev-&gt;next = <span class="built_in">reverseList</span>(start);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 连接翻转后的链表</span></span><br><span class="line">            start-&gt;next = nextGroup;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 更新 prev 和 end 为下一组的前一个节点</span></span><br><span class="line">            prev = start;</span><br><span class="line">            end = prev;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        ListNode* result = dummy-&gt;next;</span><br><span class="line">        <span class="keyword">delete</span> dummy;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 翻转整个链表</span></span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseList</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode* prev = <span class="literal">nullptr</span>;</span><br><span class="line">        ListNode* curr = head;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (curr != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            ListNode* next = curr-&gt;next;</span><br><span class="line">            curr-&gt;next = prev;</span><br><span class="line">            prev = curr;</span><br><span class="line">            curr = next;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> prev;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>两两交换链表中的节点</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">swapPairs</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!head) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode* pre = dummy;</span><br><span class="line">        ListNode* next_node = head;</span><br><span class="line">        <span class="keyword">while</span> (next_node &amp;&amp; next_node-&gt;next) &#123;</span><br><span class="line">            <span class="comment">// 交换节点</span></span><br><span class="line">            ListNode* secondNode = next_node-&gt;next;</span><br><span class="line">            pre-&gt;next = secondNode;</span><br><span class="line">            next_node-&gt;next = secondNode-&gt;next;</span><br><span class="line">            secondNode-&gt;next = next_node;</span><br><span class="line"></span><br><span class="line">            pre = next_node;</span><br><span class="line">            next_node = pre-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>递归,递归的终止条件是链表中没有节点，或者链表中只有一个节点，此时无法进行交换。</p><p><strong>递归过程</strong>：</p><ol><li>设当前头节点为 <code>head</code>，下一个节点为 <code>next</code>。</li><li><code>head</code> 的下一个节点应该是“后续所有节点两两交换后”的结果。</li><li><code>next</code> 的下一个节点指向当前的 <code>head</code>。</li><li>返回 <code>next</code> 作为这部分交换后的新头节点。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">swapPairs</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (head == <span class="literal">nullptr</span> || head-&gt;next == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> head;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* newHead = head-&gt;next;</span><br><span class="line">        head-&gt;next = <span class="built_in">swapPairs</span>(newHead-&gt;next);</span><br><span class="line">        newHead-&gt;next = head;</span><br><span class="line">        <span class="keyword">return</span> newHead;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>交换链表中的节点</strong></p><p>给你链表的头节点 <code>head</code> 和一个整数 <code>k</code> 。</p><p><strong>交换</strong> 链表正数第 <code>k</code> 个节点和倒数第 <code>k</code> 个节点的值后，返回链表的头节点（链表 <strong>从 1 开始索引</strong>）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">swapNodes</span><span class="params">(ListNode* head, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 快慢指针</span></span><br><span class="line">        ListNode *slow = head, *fast = head;</span><br><span class="line">        k--;</span><br><span class="line">        <span class="keyword">while</span> (k--) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* node1 = fast;</span><br><span class="line">        <span class="keyword">while</span>(fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        ListNode* node2 = slow;</span><br><span class="line">        <span class="built_in">swap</span>(node1-&gt;val,node2-&gt;val);</span><br><span class="line">        <span class="keyword">return</span> head;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>翻转偶数长度组的节点</strong></p><p>给你一个链表的头节点 <code>head</code> 。</p><p>链表中的节点 <strong>按顺序</strong> 划分成若干 <strong>非空</strong> 组，这些非空组的长度构成一个自然数序列（<code>1, 2, 3, 4, ...</code>）。一个组的 <strong>长度</strong> 就是组中分配到的节点数目。换句话说：</p><ul><li>节点 <code>1</code> 分配给第一组</li><li>节点 <code>2</code> 和 <code>3</code> 分配给第二组</li><li>节点 <code>4</code>、<code>5</code> 和 <code>6</code> 分配给第三组，以此类推</li></ul><p>注意，最后一组的长度可能小于或者等于 <code>1 + 倒数第二组的长度</code> 。</p><p><strong>反转</strong> 每个 <strong>偶数</strong> 长度组中的节点，并返回修改后链表的头节点 <code>head</code></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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseEvenLengthGroups</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建虚拟头节点，简化边界处理</span></span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> <span class="built_in">ListNode</span>(<span class="number">0</span>);</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// pre: 当前组的前一个节点（上一组的尾节点）</span></span><br><span class="line">        <span class="comment">// tail: 用于遍历找到当前组的尾节点</span></span><br><span class="line">        ListNode* pre = dummy;</span><br><span class="line">        ListNode* tail = dummy;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> groupSize = <span class="number">1</span>;  <span class="comment">// 当前组期望的长度</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 循环处理每一组</span></span><br><span class="line">        <span class="keyword">while</span> (tail-&gt;next != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            <span class="type">int</span> actualCount = <span class="number">0</span>;  <span class="comment">// 当前组实际有多少个节点</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 步骤1: 找到当前组的尾节点</span></span><br><span class="line">            <span class="comment">// 尝试走 groupSize 步，但可能走不完（最后一组）</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; groupSize &amp;&amp; tail-&gt;next != <span class="literal">nullptr</span>; i++) &#123;</span><br><span class="line">                tail = tail-&gt;next;  <span class="comment">// 移动到当前组的下一个节点</span></span><br><span class="line">                actualCount++;      <span class="comment">// 统计实际节点数</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 步骤2: 判断是否需要反转</span></span><br><span class="line">            <span class="keyword">if</span> (actualCount % <span class="number">2</span> == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 情况A: 需要反转当前组（实际长度为偶数）</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 记录当前组的起始节点和下一组的起始节点</span></span><br><span class="line">                ListNode* groupStart = pre-&gt;next;  <span class="comment">// 当前组的第一个节点</span></span><br><span class="line">                ListNode* nextGroupStart = tail-&gt;next;  <span class="comment">// 下一组的第一个节点</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 断开当前组与下一组的连接，便于单独反转</span></span><br><span class="line">                tail-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 反转当前组</span></span><br><span class="line">                ListNode* reversedGroupHead = <span class="built_in">reverseList</span>(groupStart);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 重新连接链表</span></span><br><span class="line">                pre-&gt;next = reversedGroupHead;  <span class="comment">// 上一组的尾连接反转后的组头</span></span><br><span class="line">                groupStart-&gt;next = nextGroupStart;  <span class="comment">// 反转前的组头（现在是组尾）连接下一组</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 更新 pre 和 tail 为当前组的尾节点（反转前的头节点）</span></span><br><span class="line">                pre = groupStart;</span><br><span class="line">                tail = groupStart;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 情况B: 不需要反转（实际长度为奇数）</span></span><br><span class="line">                <span class="comment">// 直接让 pre 指向当前组的尾节点，为下一组做准备</span></span><br><span class="line">                pre = tail;</span><br><span class="line">                <span class="comment">// tail 已经在正确位置，不需要移动</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 步骤3: 准备处理下一组，期望长度+1</span></span><br><span class="line">            groupSize++;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        ListNode* result = dummy-&gt;next;</span><br><span class="line">        <span class="keyword">delete</span> dummy;  <span class="comment">// 清理虚拟头节点</span></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 反转链表的辅助函数</span></span><br><span class="line">    <span class="function">ListNode* <span class="title">reverseList</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        ListNode* prev = <span class="literal">nullptr</span>;  <span class="comment">// 前一个节点</span></span><br><span class="line">        ListNode* curr = head;     <span class="comment">// 当前节点</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (curr != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">            ListNode* nextNode = curr-&gt;next;  <span class="comment">// 保存下一个节点</span></span><br><span class="line">            curr-&gt;next = prev;                <span class="comment">// 反转指针方向</span></span><br><span class="line">            prev = curr;                      <span class="comment">// 前移prev</span></span><br><span class="line">            curr = nextNode;                  <span class="comment">// 前移curr</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> prev;  <span class="comment">// 返回新的头节点</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>删除链表的倒数第N个节点</strong></p><p>给你一个链表，删除链表的倒数第 <code>n</code> 个结点，并且返回链表的头结点。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">removeNthFromEnd</span><span class="params">(ListNode* head, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 双指针</span></span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> ListNode;</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode *slow = dummy, *fast = dummy;</span><br><span class="line">        <span class="keyword">while</span> (n--) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (fast &amp;&amp; fast-&gt;next) &#123;</span><br><span class="line">            fast = fast-&gt;next;</span><br><span class="line">            slow = slow-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        slow-&gt;next = slow-&gt;next-&gt;next;</span><br><span class="line">        <span class="keyword">return</span> dummy-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>分隔链表</strong></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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;ListNode*&gt; <span class="title">splitListToParts</span><span class="params">(ListNode* head, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 计算总长度 N</span></span><br><span class="line">        <span class="type">int</span> n = <span class="number">0</span>;</span><br><span class="line">        ListNode* curr = head;</span><br><span class="line">        <span class="keyword">while</span> (curr) &#123;</span><br><span class="line">            n++;</span><br><span class="line">            curr = curr-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 计算每部分的基础长度和需要多分配一个节点的数量</span></span><br><span class="line">        <span class="type">int</span> avg_len = n / k;</span><br><span class="line">        <span class="type">int</span> remainder = n % k;</span><br><span class="line"></span><br><span class="line">        <span class="function">vector&lt;ListNode*&gt; <span class="title">res</span><span class="params">(k, <span class="literal">nullptr</span>)</span></span>;</span><br><span class="line">        curr = head;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 循环 k 次，切分出 k 个部分</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; k &amp;&amp; curr != <span class="literal">nullptr</span>; ++i) &#123;</span><br><span class="line">            res[i] = curr; <span class="comment">// 当前部分的头节点</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 计算当前部分应该有的长度</span></span><br><span class="line">            <span class="type">int</span> current_part_size = avg_len + (i &lt; remainder ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 走到当前部分的最后一个节点</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; current_part_size; ++j) &#123;</span><br><span class="line">                curr = curr-&gt;next;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 断开连接，并指向下一个部分的起点</span></span><br><span class="line">            ListNode* next_part_start = curr-&gt;next;</span><br><span class="line">            curr-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line">            curr = next_part_start;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="数字-数论题目"><a href="#数字-数论题目" class="headerlink" title="数字/数论题目"></a>数字/数论题目</h1><p>数根又称数字根（Digital root），是自然数的一种性质，每个自然数都有一个数根。<strong>对于给定的自然数，反复将各个位上的数字相加，直到结果为一位数，则该一位数即为原自然数的数根。</strong></p><p>计算数根的最直观的方法是模拟计算各位相加的过程，直到剩下的数字是一位数。利用自然数的性质，则能在 O(1) 的时间内计算数根。</p><p>给定一个非负整数 <code>num</code>，反复将各个位上的数字相加，直到结果为一位数。返回这个结果。</p><p>对 num 分类讨论：</p><p>num 不是 9 的倍数时，其数根即为 num 除以 9 的余数。</p><p>num 是 9 的倍数时：</p><p>如果 num=0，则其数根是 0；</p><p>如果 num&gt;0，则各位相加的结果大于 0，其数根也大于 0，因此其数根是 </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">addDigits</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (num - <span class="number">1</span>) % <span class="number">9</span> + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>丑数</strong></p><p><strong>丑数</strong> 就是只包含质因数 <code>2</code>、<code>3</code> 和 <code>5</code> 的 <em>正</em> 整数。</p><p>给你一个整数 <code>n</code> ，请你判断 <code>n</code> 是否为 <strong>丑数</strong> 。如果是，返回 <code>true</code> ；否则，返回 <code>false</code> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isUgly</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(n&lt;=<span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; nums&#123;<span class="number">2</span>,<span class="number">3</span>,<span class="number">5</span>&#125;;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; num:nums) &#123;</span><br><span class="line">            <span class="keyword">while</span>(n%num==<span class="number">0</span>) &#123;</span><br><span class="line">                n/=num;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> n==<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>给你一个整数 <code>n</code> ，请你找出并返回第 <code>n</code> 个 <strong>丑数</strong> 。<strong>丑数</strong> 就是质因子只包含 <code>2</code>、<code>3</code> 和 <code>5</code> 的正整数</p><p>我们要维护一个有序的丑数列表。假设我们已经有了前几个丑数，下一个丑数一定是：</p><ul><li>之前的某个丑数 $\times 2$</li><li>之前的某个丑数 $\times 3$</li><li>之前的某个丑数 $\times 5$</li></ul><p>为了保证列表的有序性，我们每次都选这三个乘积中<strong>最小</strong>的那一个。</p><p><strong>算法逻辑</strong>：</p><ol><li>定义数组 <code>dp</code>，<code>dp[i]</code> 表示第 $i+1$ 个丑数。初始化 <code>dp[0] = 1</code>。</li><li>定义三个指针 <code>p2, p3, p5</code>，初始都指向下标 <code>0</code>。</li><li>循环 $n-1$ 次：<ul><li>计算 <code>next2 = dp[p2] * 2</code>, <code>next3 = dp[p3] * 3</code>, <code>next5 = dp[p5] * 5</code>。</li><li>取三者最小值作为下一个丑数：<code>dp[i] = min(next2, next3, next5)</code>。</li><li><strong>关键点</strong>：哪个指针产生的最小值，就把哪个指针<strong>向后移一位</strong>。如果多个指针产生的乘积相同（比如 $2 \times 3 = 6$ 和 $3 \times 2 = 6$），则这些指针<strong>都要移动</strong>，以实现去重。</li></ul></li></ol><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><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 生成最小质因数</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">nthUglyNumber</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">dp</span><span class="params">(n)</span></span>;</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> n2&#123;&#125;, n3&#123;&#125;, n5&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="type">int</span> p2 = dp[n2] * <span class="number">2</span>;</span><br><span class="line">            <span class="type">int</span> p3 = dp[n3] * <span class="number">3</span>;</span><br><span class="line">            <span class="type">int</span> p5 = dp[n5] * <span class="number">5</span>;</span><br><span class="line">            <span class="type">int</span> min_num = <span class="built_in">min</span>(<span class="built_in">min</span>(p2, p3), p5);</span><br><span class="line">            dp[i] = min_num;</span><br><span class="line">            <span class="keyword">if</span> (min_num == p2) &#123;</span><br><span class="line">                n2++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (min_num == p3) &#123;</span><br><span class="line">                n3++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (min_num == p5) &#123;</span><br><span class="line">                n5++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[n - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以把寻找丑数的过程看作是<strong>合并三个有序链表</strong>：</p><ul><li>链表 1：$1\times2, 2\times2, 3\times2, 4\times2, 5\times2, \dots$</li><li>链表 2：$1\times3, 2\times3, 3\times3, 4\times3, 5\times3, \dots$</li><li>链表 3：$1\times5, 2\times5, 3\times5, 4\times5, 5\times5, \dots$</li></ul><p>由于我们每次都从这三个链表的“头部”取最小值，且“头部”的定义是由 <code>p2, p3, p5</code> 指向的 <code>dp</code> 元素决定的，因此我们能保证生成的 <code>dp</code> 数组是严格递增且不遗漏任何丑数的。</p><p>丑数是可以被 <code>a</code> <strong>或</strong> <code>b</code> <strong>或</strong> <code>c</code> 整除的 <strong>正整数</strong> 。</p><p>给你四个整数：<code>n</code> 、<code>a</code> 、<code>b</code> 、<code>c</code> ，请你设计一个算法来找出第 <code>n</code> 个丑数。 二分+容斥原理</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">gcd</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>&#123; <span class="keyword">return</span> b == <span class="number">0</span> ? a : <span class="built_in">gcd</span>(b, a % b); &#125;</span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">lcm</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>&#123; <span class="keyword">return</span> (<span class="type">long</span> <span class="type">long</span>)a / <span class="built_in">gcd</span>(a, b) * b; &#125;</span><br><span class="line">    <span class="comment">// 计算[1, x]范围内丑数的个数（容斥原理）</span></span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">count</span><span class="params">(<span class="type">long</span> <span class="type">long</span> x, <span class="type">long</span> <span class="type">long</span> a, <span class="type">long</span> <span class="type">long</span> b, <span class="type">long</span> <span class="type">long</span> c, <span class="type">long</span> <span class="type">long</span> lcm_ab,</span></span></span><br><span class="line"><span class="params"><span class="function">                    <span class="type">long</span> <span class="type">long</span> lcm_ac, <span class="type">long</span> <span class="type">long</span> lcm_bc, <span class="type">long</span> <span class="type">long</span> lcm_abc)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (x / a + x / b + x / c - x / lcm_ab - x / lcm_ac - x / lcm_bc +</span><br><span class="line">                x / lcm_abc);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">nthUglyNumber</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span> a, <span class="type">int</span> b, <span class="type">int</span> c)</span> </span>&#123;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> lcm_ab = <span class="built_in">lcm</span>(a, b);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> lcm_ac = <span class="built_in">lcm</span>(a, c);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> lcm_bc = <span class="built_in">lcm</span>(c, b);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> lcm_abc = <span class="built_in">lcm</span>(lcm_ab, c);</span><br><span class="line">        <span class="type">int</span> left = <span class="number">1</span>, right = <span class="number">2e9</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right - left) / <span class="number">2</span> + left;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">count</span>(mid, a, b, c, lcm_ab, lcm_ac, lcm_bc, lcm_abc) &lt; n) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> left;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>超级丑数</strong> 是一个正整数，并满足其所有质因数都出现在质数数组 <code>primes</code> 中。给你一个整数 <code>n</code> 和一个整数数组 <code>primes</code> ，返回第 <code>n</code> 个 <strong>超级丑数</strong> 。题目数据保证第 <code>n</code> 个 <strong>超级丑数</strong> 在 <strong>32-bit</strong> 带符号整数范围内。</p><p>定义数组 dp，其中 dp[i] 表示第 i 个超级丑数，第 n 个超级丑数即为 dp[n]。</p><p>由于最小的超级丑数是 1，因此 dp[1]=1。</p><p>如何得到其余的超级丑数呢？创建与数组 primes 相同长度的数组 pointers，表示下一个超级丑数是当前指针指向的超级丑数乘以对应的质因数。初始时，数组 pointers 的元素值都是 1。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">nthSuperUglyNumber</span><span class="params">(<span class="type">int</span> n, vector&lt;<span class="type">int</span>&gt;&amp; primes)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">long</span>&gt; <span class="title">dp</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> m = primes.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">pointers</span><span class="params">(m, <span class="number">0</span>)</span></span>;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">long</span>&gt; <span class="title">nums</span><span class="params">(m, <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="type">long</span> minNum = INT_MAX;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; m; j++) &#123;</span><br><span class="line">                minNum = <span class="built_in">min</span>(minNum, nums[j]);</span><br><span class="line">            &#125;</span><br><span class="line">            dp[i] = minNum;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; m; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (nums[j] == minNum) &#123;</span><br><span class="line">                    pointers[j]++;</span><br><span class="line">                    nums[j] = dp[pointers[j]] * primes[j];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>快乐数</strong></p><p>编写一个算法来判断一个数 <code>n</code> 是不是快乐数。</p><p><strong>「快乐数」</strong> 定义为：</p><ul><li>对于一个正整数，每一次将该数替换为它每个位置上的数字的平方和。</li><li>然后重复这个过程直到这个数变为 1，也可能是 <strong>无限循环</strong> 但始终变不到 1。</li><li>如果这个过程 <strong>结果为</strong> 1，那么这个数就是快乐数。</li></ul><p>如果 <code>n</code> 是 <em>快乐数</em> 就返回 <code>true</code> ；不是，则返回 <code>false</code> </p><p>快慢指针,通过反复调用 getNext(n) 得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针，但数据仍然形成链表结构。起始数字是链表的头 “节点”，链中的所有其他数字都是节点。next 指针是通过调用 getNext(n) 函数获得。意识到实际有个链表，那么这个问题就<strong>可以转换为检测一个链表是否有环</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getNext</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">while</span>(num) &#123;</span><br><span class="line">            res += (num%<span class="number">10</span>)*(num%<span class="number">10</span>);</span><br><span class="line">            num/=<span class="number">10</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isHappy</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> slowNum = n,fastNum = <span class="built_in">getNext</span>(n);</span><br><span class="line">        <span class="keyword">while</span>(fastNum!= <span class="number">1</span> &amp;&amp; fastNum!=slowNum) &#123;</span><br><span class="line">            slowNum = <span class="built_in">getNext</span>(slowNum);</span><br><span class="line">            fastNum = <span class="built_in">getNext</span>(<span class="built_in">getNext</span>(fastNum));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> fastNum == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>加一</strong></p><p>给定一个表示 <strong>大整数</strong> 的整数数组 <code>digits</code>，其中 <code>digits[i]</code> 是整数的第 <code>i</code> 位数字。这些数字按从左到右，从最高位到最低位排列。这个大整数不包含任何前导 <code>0</code>。将大整数加 1，并返回结果的数字数组。</p><p>只需要对数组 digits 进行一次逆序遍历，找出第一个不为 9 的元素，将其加一并将后续所有元素置零即可。如果 digits 中所有的元素均为 9，那么对应着「思路」部分的第三种情况，我们需要返回一个新的数组。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">plusOne</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; digits)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz =digits.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = sz<span class="number">-1</span>;i&gt;=<span class="number">0</span>;i--) &#123;</span><br><span class="line">            <span class="keyword">if</span>(digits[i]!=<span class="number">9</span>) &#123;</span><br><span class="line">                digits[i] +=<span class="number">1</span>;</span><br><span class="line">                <span class="keyword">for</span>(<span class="type">int</span> j = i<span class="number">+1</span>;j&lt;sz;j++) &#123;</span><br><span class="line">                    digits[j] = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> digits;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//全为9</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">ans</span><span class="params">(<span class="number">1</span>+sz)</span></span>;</span><br><span class="line">        ans[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>给单链表加一</strong></p><p>给定一个用<strong>链表</strong>表示的非负整数， 然后将这个整数 <em>再加上 1</em> 。</p><p>这些数字的存储是这样的：最高位有效的数字位于链表的首位 <code>head</code> 。</p><p><strong>算法步骤</strong></p><ul><li><p>初始化哨兵节点为 ListNode(0) 并将其设置为新的头节点：sentinel.next = head。</p></li><li><p>找到最右边的不等于九的数字。</p></li><li>将该数字加一。</li><li>将所有后面的九都设为零。</li><li>如果哨兵节点被设置为1，则返回哨兵节点， 否则返回头 sentinel.next。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">plusOne</span><span class="params">(ListNode* head)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 找到最右边的非9数字</span></span><br><span class="line">        ListNode* dummy = <span class="keyword">new</span> <span class="built_in">ListNode</span>(<span class="number">0</span>);</span><br><span class="line">        dummy-&gt;next = head;</span><br><span class="line">        ListNode* flag = dummy;</span><br><span class="line">        <span class="keyword">while</span> (head) &#123;</span><br><span class="line">            <span class="keyword">if</span> (head-&gt;val != <span class="number">9</span>) &#123;</span><br><span class="line">                flag = head;</span><br><span class="line">            &#125;</span><br><span class="line">            head = head-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        flag-&gt;val += <span class="number">1</span>;</span><br><span class="line">        flag = flag-&gt;next;</span><br><span class="line">        <span class="comment">// 将后面的值置为0</span></span><br><span class="line">        <span class="keyword">while</span> (flag) &#123;</span><br><span class="line">            flag-&gt;val = <span class="number">0</span>;</span><br><span class="line">            flag = flag-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> (dummy-&gt;val == <span class="number">0</span>)?dummy-&gt;next:dummy;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>数组形式整数加法</strong></p><p>整数的 <strong>数组形式</strong> <code>num</code> 是按照从左到右的顺序表示其数字的数组。</p><ul><li>例如，对于 <code>num = 1321</code> ，数组形式是 <code>[1,3,2,1]</code> 。</li></ul><p>给定 <code>num</code> ，整数的 <strong>数组形式</strong> ，和整数 <code>k</code> ，返回 <em>整数 <code>num + k</code> 的 <strong>数组形式</strong></em> 。</p><p>任何时候，若加法的结果大于等于 10，把进位的 1 加入到下一位的计算中，所以最终结果为 1035。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">addToArrayForm</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; num, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = num.<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; ans;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = sz - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="type">int</span> val = num[i] + k % <span class="number">10</span>;</span><br><span class="line">            k /= <span class="number">10</span>;</span><br><span class="line">            k += val / <span class="number">10</span>;</span><br><span class="line">            ans.<span class="built_in">push_back</span>(val % <span class="number">10</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (k&gt;<span class="number">0</span>) &#123;</span><br><span class="line">            ans.<span class="built_in">push_back</span>(k % <span class="number">10</span>);</span><br><span class="line">            k /= <span class="number">10</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">reverse</span>(ans.<span class="built_in">begin</span>(),ans.<span class="built_in">end</span>());</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>将整数减少到零需要的最少操作数</strong></p><p>给你一个正整数 <code>n</code> ，你可以执行下述操作 <strong>任意</strong> 次：</p><ul><li><code>n</code> 加上或减去 <code>2</code> 的某个 <strong>幂</strong></li></ul><p>返回使 <code>n</code> 等于 <code>0</code> 需要执行的 <strong>最少</strong> 操作数。</p><p>如果 <code>x == 2i</code> 且其中 <code>i &gt;= 0</code> ，则数字 <code>x</code> 是 <code>2</code> 的幂。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt; memo;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(num)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[num];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> ((num &amp; <span class="number">1</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 00 10</span></span><br><span class="line">            memo[num] = <span class="built_in">dfs</span>(num &gt;&gt; <span class="number">1</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果是奇数</span></span><br><span class="line">            <span class="comment">//  01 11</span></span><br><span class="line">            <span class="keyword">if</span> ((num &amp; <span class="number">3</span>) == <span class="number">3</span>) &#123;</span><br><span class="line">                memo[num] = <span class="number">1</span> + <span class="built_in">dfs</span>(num + <span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                memo[num] = <span class="number">1</span> + <span class="built_in">dfs</span>(num - <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> memo[num];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minOperations</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 如果是偶数 00 10 直接减去</span></span><br><span class="line">        memo[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(n);</span><br><span class="line">    &#125;</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_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, <span class="type">int</span>&gt; memo;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">dfs</span><span class="params">(<span class="type">int</span> num)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(num)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[num];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> r&#123;&#125;;</span><br><span class="line">        <span class="keyword">if</span> ((num &amp; (num - <span class="number">1</span>)) == <span class="number">0</span>) &#123;</span><br><span class="line">            r = <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">int</span> lb = num &amp; (-num);</span><br><span class="line">            r = <span class="number">1</span> + <span class="built_in">min</span>(<span class="built_in">dfs</span>(num + lb), <span class="built_in">dfs</span>(num - lb));</span><br><span class="line">        &#125;</span><br><span class="line">        memo[num] = r;</span><br><span class="line">        <span class="keyword">return</span> r;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minOperations</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 如果是偶数 00 10 直接减去</span></span><br><span class="line">        memo[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">dfs</span>(n);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>计算字符串的数字和</strong></p><p>给你一个由若干数字（<code>0</code> - <code>9</code>）组成的字符串 <code>s</code> ，和一个整数。</p><p>如果 <code>s</code> 的长度大于 <code>k</code> ，则可以执行一轮操作。在一轮操作中，需要完成以下工作：</p><ol><li>将 <code>s</code> <strong>拆分</strong> 成长度为 <code>k</code> 的若干 <strong>连续数字组</strong> ，使得前 <code>k</code> 个字符都分在第一组，接下来的 <code>k</code> 个字符都分在第二组，依此类推。<strong>注意</strong>，最后一个数字组的长度可以小于 <code>k</code> 。</li><li>用表示每个数字组中所有数字之和的字符串来 <strong>替换</strong> 对应的数字组。例如，<code>&quot;346&quot;</code> 会替换为 <code>&quot;13&quot;</code> ，因为 <code>3 + 4 + 6 = 13</code> 。</li><li><strong>合并</strong> 所有组以形成一个新字符串。如果新字符串的长度大于 <code>k</code> 则重复第一步。</li></ol><p>返回在完成所有轮操作后的 <code>s</code> 。</p><p>以模拟题目中的操作过程更新字符串 s，具体在每一轮操作中：</p><p>我们用字符串（或数组，视不同语言字符串的实现方式而确定）tmp 来维护该轮操作的结果。随后，我们遍历字符串 s，以每 k 个字符为一组，计算该组的数字和 val，并转化为字符串添加至 tmp 尾部。最终，我们将 s 更新为 tmp 所表示的字符串。</p><p>我们执行上述操作直到 s 的长度小于等于 k 为止，并最终返回 s 作为答案。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">digitSum</span><span class="params">(string s, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (s.<span class="built_in">size</span>() &gt; k) &#123;</span><br><span class="line">            string ns;</span><br><span class="line">            <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; s.<span class="built_in">size</span>(); i += k) &#123;</span><br><span class="line">                <span class="type">int</span> num&#123;&#125;;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> j = i; j &lt; i + k &amp;&amp; j &lt; sz; j++) &#123;</span><br><span class="line">                    num += (s[j] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                ns += <span class="built_in">to_string</span>(num);</span><br><span class="line">            &#125;</span><br><span class="line">            s = ns;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> s;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><img data-src="https://files.seeusercontent.com/2026/02/21/Sr6n/image-20260221213221536.png" alt="image-20260221213221536"></p><h1 id="图算法"><a href="#图算法" class="headerlink" title="图算法"></a>图算法</h1><h2 id="最短路径"><a href="#最短路径" class="headerlink" title="最短路径"></a>最短路径</h2><p>最短路是图论中的经典问题即：给出一个有向图，一个起点，一个终点，问起点到终点的最短路径。</p><h4 id="dijkstra"><a href="#dijkstra" class="headerlink" title="dijkstra"></a>dijkstra</h4><p>dijkstra算法：在有权图（权值非负数）中求从起点到其他节点的最短路径算法。</p><p>需要注意两点：</p><ul><li>dijkstra 算法可以同时求 起点到所有节点的最短路径</li><li>权值不能为负数</li></ul><p>dijkstra 算法是贪心的思路，不断寻找距离 源点最近的没有访问过的节点。<strong>minDist数组 用来记录每一个节点距离源点的最小距离</strong>。</p><p> <strong>dijkstra三部曲</strong>：</p><ol><li>第一步，选源点到哪个节点近且该节点未被访问过</li><li>第二步，该最近节点被标记访问过</li><li>第三步，更新非访问节点到源点的距离（即更新minDist数组）</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, m, p1, p2, val;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">grid</span>(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">1</span>, INT_MAX));</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val;</span><br><span class="line">        grid[p1][p2] = val;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> start = <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> end = n;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(n + <span class="number">1</span>, INT_MAX)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::vector&lt;<span class="type">bool</span>&gt; <span class="title">visited</span><span class="params">(n + <span class="number">1</span>, <span class="literal">false</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    minDist[start] = <span class="number">0</span>; </span><br><span class="line"></span><br><span class="line">    <span class="comment">//加上初始化</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">parent</span><span class="params">(n + <span class="number">1</span>, <span class="number">-1</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> minVal = INT_MAX;</span><br><span class="line">        <span class="type">int</span> cur = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> v = <span class="number">1</span>; v &lt;= n; ++v) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[v] &amp;&amp; minDist[v] &lt; minVal) &#123;</span><br><span class="line">                minVal = minDist[v];</span><br><span class="line">                cur = v;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        visited[cur] = <span class="literal">true</span>;</span><br><span class="line"><span class="comment">// 更新权重</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> v = <span class="number">1</span>; v &lt;= n; v++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[v] &amp;&amp; grid[cur][v] != INT_MAX &amp;&amp; minDist[cur] + grid[cur][v] &lt; minDist[v]) &#123;</span><br><span class="line">                minDist[v] = minDist[cur] + grid[cur][v];</span><br><span class="line">                parent[v] = cur; <span class="comment">// 记录边</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 输出最短情况</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        cout &lt;&lt; parent[i] &lt;&lt; <span class="string">&quot;-&gt;&quot;</span> &lt;&lt; i &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>邻接表存储+堆优化版本,其实思路依然是 dijkstra 三部曲：</p><ol><li>第一步，选源点到哪个节点近且该节点未被访问过</li><li>第二步，该最近节点被标记访问过</li><li>第三步，更新非访问节点到源点的距离（即更新minDist数组）</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 小顶堆</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">mycomparison</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&amp; lhs, <span class="type">const</span> pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&amp; rhs)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> lhs.second &gt; rhs.second;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// 定义一个结构体来表示带权重的边</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Edge</span> &#123;</span><br><span class="line">    <span class="type">int</span> to;  <span class="comment">// 邻接顶点</span></span><br><span class="line">    <span class="type">int</span> val; <span class="comment">// 边的权重</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">Edge</span>(<span class="type">int</span> t, <span class="type">int</span> w): <span class="built_in">to</span>(t), <span class="built_in">val</span>(w) &#123;&#125;  <span class="comment">// 构造函数</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, m, p1, p2, val;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line"></span><br><span class="line">    vector&lt;list&lt;Edge&gt;&gt; <span class="built_in">grid</span>(n + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val; </span><br><span class="line">        <span class="comment">// p1 指向 p2，权值为 val</span></span><br><span class="line">        grid[p1].<span class="built_in">push_back</span>(<span class="built_in">Edge</span>(p2, val));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> start = <span class="number">1</span>;  <span class="comment">// 起点</span></span><br><span class="line">    <span class="type">int</span> end = n;    <span class="comment">// 终点</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存储从源点到每个节点的最短距离</span></span><br><span class="line">    <span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(n + <span class="number">1</span>, INT_MAX)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 记录顶点是否被访问过</span></span><br><span class="line">    <span class="function">std::vector&lt;<span class="type">bool</span>&gt; <span class="title">visited</span><span class="params">(n + <span class="number">1</span>, <span class="literal">false</span>)</span></span>; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 优先队列中存放 pair&lt;节点，源点到该节点的权值&gt;</span></span><br><span class="line">    priority_queue&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;, vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt;, mycomparison&gt; pq;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化队列，源点到源点的距离为0，所以初始为0</span></span><br><span class="line">    pq.<span class="built_in">push</span>(<span class="built_in">pair</span>&lt;<span class="type">int</span>, <span class="type">int</span>&gt;(start, <span class="number">0</span>)); </span><br><span class="line">    </span><br><span class="line">    minDist[start] = <span class="number">0</span>;  <span class="comment">// 起始点到自身的距离为0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!pq.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        <span class="comment">// 1. 第一步，选源点到哪个节点近且该节点未被访问过 （通过优先级队列来实现）</span></span><br><span class="line">        <span class="comment">// &lt;节点， 源点到该节点的距离&gt;</span></span><br><span class="line">        pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt; cur = pq.<span class="built_in">top</span>(); pq.<span class="built_in">pop</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (visited[cur.first]) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 第二步，该最近节点被标记访问过</span></span><br><span class="line">        visited[cur.first] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 第三步，更新非访问节点到源点的距离（即更新minDist数组）</span></span><br><span class="line">        <span class="keyword">for</span> (Edge edge : grid[cur.first]) &#123; <span class="comment">// 遍历 cur指向的节点，cur指向的节点为 edge</span></span><br><span class="line">            <span class="comment">// cur指向的节点edge.to，这条边的权值为 edge.val</span></span><br><span class="line">            <span class="keyword">if</span> (!visited[edge.to] &amp;&amp; minDist[cur.first] + edge.val &lt; minDist[edge.to]) &#123; <span class="comment">// 更新minDist</span></span><br><span class="line">                minDist[edge.to] = minDist[cur.first] + edge.val;</span><br><span class="line">                pq.<span class="built_in">push</span>(<span class="built_in">pair</span>&lt;<span class="type">int</span>, <span class="type">int</span>&gt;(edge.to, minDist[edge.to]));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (minDist[end] == INT_MAX) cout &lt;&lt; <span class="number">-1</span> &lt;&lt; endl; <span class="comment">// 不能到达终点</span></span><br><span class="line">    <span class="keyword">else</span> cout &lt;&lt; minDist[end] &lt;&lt; endl; <span class="comment">// 到达终点最短路径</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="Bellman-ford"><a href="#Bellman-ford" class="headerlink" title="Bellman_ford"></a>Bellman_ford</h4><p><strong>带负权值的单源最短路问题</strong>，Bellman_ford算法的核心思想是 对所有边进行松弛n-1次操作（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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, m, p1, p2, val;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line"></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; grid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将所有边保存起来</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val;</span><br><span class="line">        <span class="comment">// p1 指向 p2，权值为 val</span></span><br><span class="line">        grid.<span class="built_in">push_back</span>(&#123;p1, p2, val&#125;);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> start = <span class="number">1</span>;  <span class="comment">// 起点</span></span><br><span class="line">    <span class="type">int</span> end = n;    <span class="comment">// 终点</span></span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(n + <span class="number">1</span> , INT_MAX)</span></span>;</span><br><span class="line">    minDist[start] = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; n; i++) &#123; <span class="comment">// 对所有边 松弛 n-1 次</span></span><br><span class="line">        <span class="keyword">for</span> (vector&lt;<span class="type">int</span>&gt; &amp;side : grid) &#123; <span class="comment">// 每一次松弛，都是对所有边进行松弛</span></span><br><span class="line">            <span class="type">int</span> from = side[<span class="number">0</span>]; <span class="comment">// 边的出发点</span></span><br><span class="line">            <span class="type">int</span> to = side[<span class="number">1</span>]; <span class="comment">// 边的到达点</span></span><br><span class="line">            <span class="type">int</span> price = side[<span class="number">2</span>]; <span class="comment">// 边的权值</span></span><br><span class="line">            <span class="comment">// 松弛操作 </span></span><br><span class="line">            <span class="comment">// minDist[from] != INT_MAX 防止从未计算过的节点出发</span></span><br><span class="line">            <span class="keyword">if</span> (minDist[from] != INT_MAX &amp;&amp; minDist[to] &gt; minDist[from] + price) &#123; </span><br><span class="line">                minDist[to] = minDist[from] + price;  </span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (minDist[end] == INT_MAX) cout &lt;&lt; <span class="string">&quot;unconnected&quot;</span> &lt;&lt; endl; <span class="comment">// 不能到达终点</span></span><br><span class="line">    <span class="keyword">else</span> cout &lt;&lt; minDist[end] &lt;&lt; endl; <span class="comment">// 到达终点最短路径</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>队列优化版本(SPFA)</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Edge</span> &#123; <span class="comment">//邻接表</span></span><br><span class="line">    <span class="type">int</span> to;  <span class="comment">// 链接的节点</span></span><br><span class="line">    <span class="type">int</span> val; <span class="comment">// 边的权重</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">Edge</span>(<span class="type">int</span> t, <span class="type">int</span> w): <span class="built_in">to</span>(t), <span class="built_in">val</span>(w) &#123;&#125;  <span class="comment">// 构造函数</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, m, p1, p2, val;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line"></span><br><span class="line">    vector&lt;list&lt;Edge&gt;&gt; <span class="built_in">grid</span>(n + <span class="number">1</span>); </span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isInQueue</span><span class="params">(n + <span class="number">1</span>)</span></span>; <span class="comment">// 加入优化，已经在队里里的元素不用重复添加</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将所有边保存起来</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val;</span><br><span class="line">        <span class="comment">// p1 指向 p2，权值为 val</span></span><br><span class="line">        grid[p1].<span class="built_in">push_back</span>(<span class="built_in">Edge</span>(p2, val));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> start = <span class="number">1</span>;  <span class="comment">// 起点</span></span><br><span class="line">    <span class="type">int</span> end = n;    <span class="comment">// 终点</span></span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(n + <span class="number">1</span> , INT_MAX)</span></span>;</span><br><span class="line">    minDist[start] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    queue&lt;<span class="type">int</span>&gt; que;</span><br><span class="line">    que.<span class="built_in">push</span>(start); </span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!que.<span class="built_in">empty</span>()) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> node = que.<span class="built_in">front</span>(); que.<span class="built_in">pop</span>();</span><br><span class="line">        isInQueue[node] = <span class="literal">false</span>; <span class="comment">// 从队列里取出的时候，要取消标记，我们只保证已经在队列里的元素不用重复加入</span></span><br><span class="line">        <span class="keyword">for</span> (Edge edge : grid[node]) &#123;</span><br><span class="line">            <span class="type">int</span> from = node;</span><br><span class="line">            <span class="type">int</span> to = edge.to;</span><br><span class="line">            <span class="type">int</span> value = edge.val;</span><br><span class="line">            <span class="keyword">if</span> (minDist[to] &gt; minDist[from] + value) &#123; <span class="comment">// 开始松弛</span></span><br><span class="line">                minDist[to] = minDist[from] + value; </span><br><span class="line">                <span class="keyword">if</span> (isInQueue[to] == <span class="literal">false</span>) &#123; <span class="comment">// 已经在队列里的元素不用重复添加</span></span><br><span class="line">                    que.<span class="built_in">push</span>(to);</span><br><span class="line">                    isInQueue[to] = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (minDist[end] == INT_MAX) cout &lt;&lt; <span class="string">&quot;unconnected&quot;</span> &lt;&lt; endl; <span class="comment">// 不能到达终点</span></span><br><span class="line">    <span class="keyword">else</span> cout &lt;&lt; minDist[end] &lt;&lt; endl; <span class="comment">// 到达终点最短路径</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>判断负权回路：有负权回路的情况下，一直都会有更短的最短路，所以 松弛 第n次，minDist数组 也会发生改变再多松弛一次，看minDist数组 是否发生变化. 如果是SPFA，节点加入队列的次数超过了 n-1次 ，那么该图就一定有负权回路。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> src, dst,k ,p1, p2, val ,m , n;</span><br><span class="line">    </span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line"></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; grid;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val;</span><br><span class="line">        grid.<span class="built_in">push_back</span>(&#123;p1, p2, val&#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cin &gt;&gt; src &gt;&gt; dst &gt;&gt; k;</span><br><span class="line"></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(n + <span class="number">1</span> , INT_MAX)</span></span>;</span><br><span class="line">    minDist[src] = <span class="number">0</span>;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">minDist_copy</span><span class="params">(n + <span class="number">1</span>)</span></span>; <span class="comment">// 用来记录上一次遍历的结果</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= k + <span class="number">1</span>; i++) &#123;</span><br><span class="line">        minDist_copy = minDist; <span class="comment">// 获取上一次计算的结果</span></span><br><span class="line">        <span class="keyword">for</span> (vector&lt;<span class="type">int</span>&gt; &amp;side : grid) &#123;</span><br><span class="line">            <span class="type">int</span> from = side[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> to = side[<span class="number">1</span>];</span><br><span class="line">            <span class="type">int</span> price = side[<span class="number">2</span>];</span><br><span class="line">            <span class="comment">// 注意使用 minDist_copy 来计算 minDist </span></span><br><span class="line">            <span class="keyword">if</span> (minDist_copy[from] != INT_MAX &amp;&amp; minDist[to] &gt; minDist_copy[from] + price) &#123;  </span><br><span class="line">                <span class="comment">//在每次计算 minDist 时候，要基于 对所有边上一次松弛的 minDist 数值才行，所以我们要记录上一次松弛的minDist</span></span><br><span class="line">                minDist[to] = minDist_copy[from] + price;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (minDist[dst] == INT_MAX) cout &lt;&lt; <span class="string">&quot;unreachable&quot;</span> &lt;&lt; endl; <span class="comment">// 不能到达终点</span></span><br><span class="line">    <span class="keyword">else</span> cout &lt;&lt; minDist[dst] &lt;&lt; endl; <span class="comment">// 到达终点最短路径</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Floyd算法"><a href="#Floyd算法" class="headerlink" title="Floyd算法"></a>Floyd算法</h4><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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> n, m, p1, p2, val;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line"></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">grid</span>(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">1</span>, <span class="number">10005</span>));  <span class="comment">// 因为边的最大距离是10^4</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++)&#123;</span><br><span class="line">        cin &gt;&gt; p1 &gt;&gt; p2 &gt;&gt; val;</span><br><span class="line">        grid[p1][p2] = val;</span><br><span class="line">        grid[p2][p1] = val; <span class="comment">// 注意这里是双向图</span></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 开始 floyd</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> k = <span class="number">1</span>; k &lt;= n; k++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                grid[i][j] = <span class="built_in">min</span>(grid[i][j], grid[i][k] + grid[k][j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 输出结果</span></span><br><span class="line">    <span class="type">int</span> z, start, end;</span><br><span class="line">    cin &gt;&gt; z;</span><br><span class="line">    <span class="keyword">while</span> (z--) &#123;</span><br><span class="line">        cin &gt;&gt; start &gt;&gt; end;</span><br><span class="line">        <span class="keyword">if</span> (grid[start][end] == <span class="number">10005</span>) cout &lt;&lt; <span class="number">-1</span> &lt;&lt; endl;</span><br><span class="line">        <span class="keyword">else</span> cout &lt;&lt; grid[start][end] &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>如果遇到单源且边为正数，直接Dijkstra</strong>。至于 <strong>使用朴素版还是 堆优化版 还是取决于图的稠密度</strong>.一般情况下，可以直接用堆优化版本。</p><p><strong>如果遇到单源边可为负数，直接 Bellman-Ford</strong>，同样 SPFA 还是 Bellman-Ford 取决于图的稠密度。一般情况下，直接用 SPFA。</p><p><strong>如果有负权回路，优先 Bellman-Ford</strong>， 如果是有限节点最短路 也优先 Bellman-Ford，理由是写代码比较方便。</p><p><strong>如果是遇到多源点求最短路，直接 Floyd</strong></p><h2 id="最小生成树"><a href="#最小生成树" class="headerlink" title="最小生成树"></a>最小生成树</h2><p>最小生成树是所有节点的最小连通子图，即：以最小的成本（边的权值）将图中所有节点链接到一起。</p><h4 id="Prim"><a href="#Prim" class="headerlink" title="Prim"></a>Prim</h4><ol><li>第一步，选距离生成树最近节点</li><li>第二步，最近节点加入生成树</li><li>第三步，更新非生成树节点到生成树的距离（即更新minDist数组）</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> v, e;</span><br><span class="line">    <span class="type">int</span> x, y, k;</span><br><span class="line">    cin &gt;&gt; v &gt;&gt; e;</span><br><span class="line">    <span class="comment">// 填一个默认最大值，题目描述val最大为10000</span></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">grid</span>(v + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(v + <span class="number">1</span>, <span class="number">10001</span>));</span><br><span class="line">    <span class="keyword">while</span> (e--) &#123;</span><br><span class="line">        cin &gt;&gt; x &gt;&gt; y &gt;&gt; k;</span><br><span class="line">        <span class="comment">// 因为是双向图，所以两个方向都要填上</span></span><br><span class="line">        grid[x][y] = k;</span><br><span class="line">        grid[y][x] = k;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">parent</span><span class="params">(v + <span class="number">1</span>, <span class="number">-1</span>)</span></span>;</span><br><span class="line">    <span class="comment">// 所有节点到最小生成树的最小距离</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">minDist</span><span class="params">(v + <span class="number">1</span>, <span class="number">10001</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这个节点是否在树里</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isInTree</span><span class="params">(v + <span class="number">1</span>, <span class="literal">false</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 我们只需要循环 n-1次，建立 n - 1条边，就可以把n个节点的图连在一起</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; v; i++) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1、prim三部曲，第一步：选距离生成树最近节点</span></span><br><span class="line">        <span class="type">int</span> cur = <span class="number">-1</span>; <span class="comment">// 选中哪个节点 加入最小生成树</span></span><br><span class="line">        <span class="type">int</span> minVal = INT_MAX;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= v; j++) &#123; <span class="comment">// 1 - v，顶点编号，这里下标从1开始</span></span><br><span class="line">            <span class="comment">//  选取最小生成树节点的条件：</span></span><br><span class="line">            <span class="comment">//  （1）不在最小生成树里</span></span><br><span class="line">            <span class="comment">//  （2）距离最小生成树最近的节点</span></span><br><span class="line">            <span class="keyword">if</span> (!isInTree[j] &amp;&amp;  minDist[j] &lt; minVal) &#123;</span><br><span class="line">                minVal = minDist[j];</span><br><span class="line">                cur = j;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 2、prim三部曲，第二步：最近节点（cur）加入生成树</span></span><br><span class="line">        isInTree[cur] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3、prim三部曲，第三步：更新非生成树节点到生成树的距离（即更新minDist数组）</span></span><br><span class="line">        <span class="comment">// cur节点加入之后， 最小生成树加入了新的节点，那么所有节点到 最小生成树的距离（即minDist数组）需要更新一下</span></span><br><span class="line">        <span class="comment">// 由于cur节点是新加入到最小生成树，那么只需要关心与 cur 相连的 非生成树节点 的距离 是否比 原来 非生成树节点到生成树节点的距离更小了呢</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= v; j++) &#123;</span><br><span class="line">            <span class="comment">// 更新的条件：</span></span><br><span class="line">            <span class="comment">// （1）节点是 非生成树里的节点</span></span><br><span class="line">            <span class="comment">// （2）与cur相连的某节点的权值 比 该某节点距离最小生成树的距离小</span></span><br><span class="line">            <span class="comment">//  cur 是新加入 最小生成树的节点，那么 所有非生成树的节点距离生成树节点的最近距离 由于 cur的新加入，需要更新一下数据了</span></span><br><span class="line">            <span class="keyword">if</span> (!isInTree[j] &amp;&amp; grid[cur][j] &lt; minDist[j]) &#123;</span><br><span class="line">                minDist[j] = grid[cur][j];</span><br><span class="line">                 parent[j] = cur; <span class="comment">// 记录最小生成树的边 （注意数组指向的顺序很重要）</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 统计结果</span></span><br><span class="line">    <span class="type">int</span> result = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= v; i++) &#123; <span class="comment">// 不计第一个顶点，因为统计的是边的权值，v个节点有 v-1条边</span></span><br><span class="line">        result += minDist[i];</span><br><span class="line">    &#125;</span><br><span class="line">    cout &lt;&lt; result &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="Kruskal"><a href="#Kruskal" class="headerlink" title="Kruskal"></a>Kruskal</h4><p>kruscal的思路：</p><ul><li>边的权值排序，因为要优先选最小的边加入到生成树里</li><li>遍历排序后的边<ul><li>如果边首尾的两个节点在同一个集合，说明如果连上这条边图中会出现环</li><li>如果边首尾的两个节点不在同一个集合，加入到最小生成树，并把两个节点加入同一个集合</li></ul></li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// l,r为 边两边的节点，val为边的数值</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Edge</span> &#123;</span><br><span class="line">    <span class="type">int</span> l, r, val;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 节点数量</span></span><br><span class="line"><span class="type">int</span> n = <span class="number">10001</span>;</span><br><span class="line"><span class="comment">// 并查集标记节点关系的数组</span></span><br><span class="line"><span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">father</span><span class="params">(n, <span class="number">-1</span>)</span></span>; <span class="comment">// 节点编号是从1开始的，n要大一些</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 并查集初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; ++i) &#123;</span><br><span class="line">        father[i] = i;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并查集的查找操作</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> u)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> u == father[u] ? u : father[u] = <span class="built_in">find</span>(father[u]); <span class="comment">// 路径压缩</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并查集的加入集合</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    u = <span class="built_in">find</span>(u); <span class="comment">// 寻找u的根</span></span><br><span class="line">    v = <span class="built_in">find</span>(v); <span class="comment">// 寻找v的根</span></span><br><span class="line">    <span class="keyword">if</span> (u == v) <span class="keyword">return</span> ; <span class="comment">// 如果发现根相同，则说明在一个集合，不用两个节点相连直接返回</span></span><br><span class="line">    father[v] = u;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> v, e;</span><br><span class="line">    <span class="type">int</span> v1, v2, val;</span><br><span class="line">    vector&lt;Edge&gt; edges;</span><br><span class="line">    <span class="type">int</span> result_val = <span class="number">0</span>;</span><br><span class="line">    cin &gt;&gt; v &gt;&gt; e;</span><br><span class="line">    <span class="keyword">while</span> (e--) &#123;</span><br><span class="line">        cin &gt;&gt; v1 &gt;&gt; v2 &gt;&gt; val;</span><br><span class="line">        edges.<span class="built_in">push_back</span>(&#123;v1, v2, val&#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行Kruskal算法</span></span><br><span class="line">    <span class="comment">// 按边的权值对边进行从小到大排序</span></span><br><span class="line">    <span class="built_in">sort</span>(edges.<span class="built_in">begin</span>(), edges.<span class="built_in">end</span>(), [](<span class="type">const</span> Edge&amp; a, <span class="type">const</span> Edge&amp; b) &#123;</span><br><span class="line">            <span class="keyword">return</span> a.val &lt; b.val;</span><br><span class="line">    &#125;);</span><br><span class="line">vector&lt;Edge&gt; result; <span class="comment">// 存储最小生成树的边</span></span><br><span class="line">    <span class="comment">// 并查集初始化</span></span><br><span class="line">    <span class="built_in">init</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从头开始遍历边</span></span><br><span class="line">    <span class="keyword">for</span> (Edge edge : edges) &#123;</span><br><span class="line">        <span class="comment">// 并查集，搜出两个节点的祖先</span></span><br><span class="line">        <span class="type">int</span> x = <span class="built_in">find</span>(edge.l);</span><br><span class="line">        <span class="type">int</span> y = <span class="built_in">find</span>(edge.r);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果祖先不同，则不在同一个集合</span></span><br><span class="line">        <span class="keyword">if</span> (x != y) &#123;</span><br><span class="line">            result.<span class="built_in">push_back</span>(edge); <span class="comment">// 保存最小生成树的边</span></span><br><span class="line">            result_val += edge.val; <span class="comment">// 这条边可以作为生成树的边</span></span><br><span class="line">            <span class="built_in">join</span>(x, y); <span class="comment">// 两个节点加入到同一个集合</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    cout &lt;&lt; result_val &lt;&lt; endl;</span><br><span class="line">     <span class="comment">// 打印最小生成树的边</span></span><br><span class="line">    <span class="keyword">for</span> (Edge edge : result) &#123;</span><br><span class="line">        cout &lt;&lt; edge.l &lt;&lt; <span class="string">&quot; - &quot;</span> &lt;&lt; edge.r &lt;&lt; <span class="string">&quot; : &quot;</span> &lt;&lt; edge.val &lt;&lt; endl;</span><br><span class="line">    &#125;</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>Kruskal 与 prim 的关键区别在于，prim维护的是节点的集合，而 Kruskal 维护的是边的集合。 如果 一个图中，节点多，但边相对较少，那么使用Kruskal 更优。</p><h2 id="拓扑排序"><a href="#拓扑排序" class="headerlink" title="拓扑排序"></a>拓扑排序</h2><p>拓扑排序 是在图上的一种排序。概括来说，<strong>给出一个 有向图，把这个有向图转成线性的排序 就叫拓扑排序</strong>。当然拓扑排序也要检测这个有向图 是否有环，即存在循环依赖的情况，因为这种情况是不能做线性排序的。<strong>拓扑排序也是图论中判断有向无环图的常用方法</strong>。</p><p>同样，拓扑排序也可以检测这个有向图 是否有环，即存在循环依赖的情况。拓扑排序的一些应用场景，例如：大学排课，文件下载依赖 等等。</p><p>只要记住如下两步拓扑排序的过程，代码就容易写了：</p><ol><li>找到入度为0 的节点，加入结果集</li><li>将该节点从图中移除</li></ol><p>概括来说，<strong>给出一个 有向图，把这个有向图转成线性的排序 就叫拓扑排序</strong>。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> m, n, s, t;</span><br><span class="line">    cin &gt;&gt; n &gt;&gt; m;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">inDegree</span><span class="params">(n, <span class="number">0</span>)</span></span>; <span class="comment">// 记录每个文件的入度</span></span><br><span class="line"></span><br><span class="line">    unordered_map&lt;<span class="type">int</span>, vector&lt;<span class="type">int</span>&gt;&gt; umap;<span class="comment">// 记录文件依赖关系</span></span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; result; <span class="comment">// 记录结果</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (m--) &#123;</span><br><span class="line">        <span class="comment">// s-&gt;t，先有s才能有t</span></span><br><span class="line">        cin &gt;&gt; s &gt;&gt; t;</span><br><span class="line">        inDegree[t]++; <span class="comment">// t的入度加一</span></span><br><span class="line">        umap[s].<span class="built_in">push_back</span>(t); <span class="comment">// 记录s指向哪些文件</span></span><br><span class="line">    &#125;</span><br><span class="line">    queue&lt;<span class="type">int</span>&gt; que;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="comment">// 入度为0的文件，可以作为开头，先加入队列</span></span><br><span class="line">        <span class="keyword">if</span> (inDegree[i] == <span class="number">0</span>) que.<span class="built_in">push</span>(i);</span><br><span class="line">        <span class="comment">//cout &lt;&lt; inDegree[i] &lt;&lt; endl;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// int count = 0;</span></span><br><span class="line">    <span class="keyword">while</span> (que.<span class="built_in">size</span>()) &#123;</span><br><span class="line">        <span class="type">int</span>  cur = que.<span class="built_in">front</span>(); <span class="comment">// 当前选中的文件</span></span><br><span class="line">        que.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="comment">//count++;</span></span><br><span class="line">        result.<span class="built_in">push_back</span>(cur);</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; files = umap[cur]; <span class="comment">//获取该文件指向的文件</span></span><br><span class="line">        <span class="keyword">if</span> (files.<span class="built_in">size</span>()) &#123; <span class="comment">// cur有后续文件</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; files.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">                inDegree[files[i]] --; <span class="comment">// cur的指向的文件入度-1</span></span><br><span class="line">                <span class="keyword">if</span>(inDegree[files[i]] == <span class="number">0</span>) que.<span class="built_in">push</span>(files[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (result.<span class="built_in">size</span>() == n) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) cout &lt;&lt; result[i] &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">        cout &lt;&lt; result[n - <span class="number">1</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> cout &lt;&lt; <span class="number">-1</span> &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="并查集"><a href="#并查集" class="headerlink" title="并查集"></a>并查集</h2><p>并查集常用来解决连通性问题。</p><p>大白话就是当我们需要判断两个元素是否在同一个集合里的时候，我们就要想到用并查集。</p><p>并查集主要有两个功能：</p><ul><li>将两个元素添加到一个集合中。</li><li>判断两个元素在不在同一个集合</li></ul><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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n = <span class="number">1005</span>; <span class="comment">// n根据题目中节点数量而定，一般比节点数量大一点就好</span></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; father = <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt; (n, <span class="number">0</span>); <span class="comment">// C++里的一种数组结构</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 并查集初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; ++i) &#123;</span><br><span class="line">        father[i] = i;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 并查集里寻根的过程</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> u)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> u == father[u] ? u : father[u] = <span class="built_in">find</span>(father[u]); <span class="comment">// 路径压缩</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 u 和 v是否找到同一个根</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isSame</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    u = <span class="built_in">find</span>(u);</span><br><span class="line">    v = <span class="built_in">find</span>(v);</span><br><span class="line">    <span class="keyword">return</span> u == v;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将v-&gt;u 这条边加入并查集</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    u = <span class="built_in">find</span>(u); <span class="comment">// 寻找u的根</span></span><br><span class="line">    v = <span class="built_in">find</span>(v); <span class="comment">// 寻找v的根</span></span><br><span class="line">    <span class="keyword">if</span> (u == v) <span class="keyword">return</span> ; <span class="comment">// 如果发现根相同，则说明在一个集合，不用两个节点相连直接返回</span></span><br><span class="line">    father[v] = u;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> join函数中如何合并两棵树呢？</p><p>一定是 rank 小的树合入 到 rank大 的树，这样可以保证最后合成的树rank 最小，降低在树上查询的路径长度。</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><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n = <span class="number">1005</span>; <span class="comment">// n根据题目中节点数量而定，一般比节点数量大一点就好</span></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; father = <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt; (n, <span class="number">0</span>); <span class="comment">// C++里的一种数组结构</span></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; rank = <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt; (n, <span class="number">1</span>); <span class="comment">// 初始每棵树的高度都为1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 并查集初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; ++i) &#123;</span><br><span class="line">        father[i] = i;</span><br><span class="line">        rank[i] = <span class="number">1</span>; <span class="comment">// 也可以不写</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 并查集里寻根的过程</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">find</span><span class="params">(<span class="type">int</span> u)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> u == father[u] ? u : <span class="built_in">find</span>(father[u]);<span class="comment">// 注意这里不做路径压缩</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 u 和 v是否找到同一个根</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isSame</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    u = <span class="built_in">find</span>(u);</span><br><span class="line">    v = <span class="built_in">find</span>(v);</span><br><span class="line">    <span class="keyword">return</span> u == v;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将v-&gt;u 这条边加入并查集</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">(<span class="type">int</span> u, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    u = <span class="built_in">find</span>(u); <span class="comment">// 寻找u的根</span></span><br><span class="line">    v = <span class="built_in">find</span>(v); <span class="comment">// 寻找v的根</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (rank[u] &lt;= rank[v]) father[u] = v; <span class="comment">// rank小的树合入到rank大的树</span></span><br><span class="line">    <span class="keyword">else</span> father[v] = u;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (rank[u] == rank[v] &amp;&amp; u != v) rank[v]++; <span class="comment">// 如果两棵树高度相同，则v的高度+1，因为上面 if (rank[u] &lt;= rank[v]) father[u] = v; 注意是 &lt;=</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意到在上面的模板代码中，我是没有做路径压缩的，因为一旦做路径压缩，rank记录的高度就不准了，根据rank来判断如何合并就没有意义。其实在优化并查集查询效率的时候，只用路径压缩的思路就够了，不仅代码实现精简，而且效率足够高。按秩合并的思路并没有将树形结构尽可能的扁平化，所以在整理效率上是没有路径压缩高的。</p><h1 id="通用解题模板"><a href="#通用解题模板" class="headerlink" title="通用解题模板"></a>通用解题模板</h1><h2 id="二分法-1"><a href="#二分法-1" class="headerlink" title="二分法"></a>二分法</h2><p>普通二分法</p><figure class="highlight arcade"><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">l=<span class="number">0</span>,h=nums.size()<span class="number">-1</span>;</span><br><span class="line"><span class="keyword">while</span>(l&lt;=h) &#123;</span><br><span class="line">  <span class="built_in">mid</span> = (h-l)/<span class="number">2</span>+l;</span><br><span class="line">  <span class="keyword">if</span>(nums[<span class="built_in">mid</span>] == target) &#123;</span><br><span class="line">     <span class="keyword">return</span> <span class="built_in">mid</span>;</span><br><span class="line">  &#125;<span class="keyword">else</span> <span class="keyword">if</span>(nums[<span class="built_in">mid</span>]&lt;target) &#123;</span><br><span class="line">  l = <span class="built_in">mid</span>+<span class="number">1</span>;</span><br><span class="line">  &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    h = <span class="built_in">mid</span><span class="number">-1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br></pre></td></tr></table></figure><p><strong>二分法变种</strong></p><p>二分法变种有些二分法类型的题目，<strong>在二分时无法直接判断中间元素是否为目标元素</strong>，这类问题被称作二分法变种问题。例如在有序数组里面查找第1个大于或等于5的元素，每次判断中间元素时，无法直接断定这个元素是否是第1个大于或等于5的元素，它可能是第2个或第3个大于或等于5的元素。对于这种问题，一个简单的思路是不断地缩小查找范围，直到剩下一个元素，即目标元素。二分法变种问题大致分为两种情况。</p><p>●　查找第一个满足条件的元素，例如查找第一个大于或等于x的元素。</p><p>●　查找最后一个满足条件的元素，例如查找最后一个小于或等于x的元素。</p><p>此外需要注意边界的控制，这是二分法变种问题最容易出错的地方。若我们在循环中让l=mid，则可能产生死循环（具体细节可翻阅第6章的内容），解决方案是通过判断当前搜索范围内元素是否剩下两个（l+1==h）来跳转出去，并在最后筛选出目标元素。</p><h2 id="回溯法"><a href="#回溯法" class="headerlink" title="回溯法"></a>回溯法</h2><p>回溯法的本质是回溯思想，通常使用递归实现，因此实现回溯法的重点是<strong>实现递归。递归的实现需要考虑3个方面：搜索的设计、递归的状态及递归的结束条件。</strong></p><p>搜索的设计对求解的空间进行划分，让每一层递归都去尝试搜索一部分的解空间，直至搜索完所有可能的解空间。递归的状态状态是用来区别不同递归的，实际上也是为了搜索而设计的，应该根据搜索过程中的需求进行合理的设计。一般而言，我们至少携带一种状态——当前位置idx，它用于找到当前可以继续前进的搜索空间，以此进入下一层递归。此外，由于题目的要求，我们可能还需要携带多个状态，用来标记当前路径解的某些信息，例如携带状态path来记录当前的路径及状态cur来记录路径上的某个信息。递归的结束条件通常包括两个方面：找到可行性解，提前结束搜索；搜索完毕，已经没有可搜索的解空间。</p><h2 id="并查集-1"><a href="#并查集-1" class="headerlink" title="并查集"></a>并查集</h2><p>并查集是一种树型结构，经常被用来处理一些不相交集合的查询和合并问题。具体而言，当我们<strong>遇到需要快速连接任意两个点，并且判断任意两个点是否连通的问题时，就可以使用并查集模板来实现</strong>。</p><p>例如,班上有n名学生，其中有些人是朋友，有些则不是，他们的友谊具有传递性。如果已知A是B的朋友，B是C的朋友，那么我们可以认为A也是C的朋友。所谓的朋友圈，是指所有朋友的集合。</p><p>给定一个n×n的矩阵M，表示班级中学生之间的朋友关系。如果M[i][j]=1，表示已知第i个和第j个学生互为朋友关系，否则为未知。你必须输出所有学生中已知的朋友圈总数</p><h2 id="BFS"><a href="#BFS" class="headerlink" title="BFS"></a>BFS</h2><p>基于队列的广度优先遍历，将起始节点放入队列中，循环遍历队列中的节点，扩展节点相邻的有效节点，并将其放入队列中。</p><h2 id="滑动窗口-1"><a href="#滑动窗口-1" class="headerlink" title="滑动窗口"></a>滑动窗口</h2><p>借助双指针表示窗口的左边界和右边界，并根据题目要求不断移动指针，搜索可能存在的有效值。根据窗口大小是否固定，以及最优解为最大或最小窗口，可以将滑动窗口分为3种类型。</p><p>滑动窗口Ⅰ特征是窗口大小固定，最优解与窗口大小无关。整体的主要流程为初始化大小为init_len的滑动窗口，检查窗口是否满足条件，更新最优解，然后循环移动窗口直到抵达边界，每次将右指针right及左指针left向右移动一步，并再次检查窗口内的信息是否满足条件，更新最优解。</p><p><img data-src="https://files.seeusercontent.com/2026/03/01/Ek1l/image-20260301141615800.png" alt=""></p><p>滑动窗口Ⅱ特征是窗口大小不固定，最优解为最小窗口。整体的主要流程为初始化大小为0的滑动窗口；然后循环移动窗口直到抵达边界，每次右指针right向右移动一步，并检查窗口是否满足条件，如果是，则循环向右移动左指针left，每次移动一步，不断尝试缩小窗口直到窗口不满足条件，更新最优解。</p><p><img data-src="https://files.seeusercontent.com/2026/03/01/dN5m/image-20260301141822219.png" alt=""></p><p>滑动窗口Ⅲ特征是窗口大小不固定，最优解为最大窗口。整体的主要流程为初始化大小为0的滑动窗口；然后循环移动窗口直到抵达边界，每次右指针right向右移动一步，并检查窗口是否不满足条件，如果是，则循环向右移动左指针left，每次移动一步直到满足条件，然后更新最优解。</p><p><img data-src="https://files.seeusercontent.com/2026/03/01/7qrX/image-20260301142133013.png" alt="image-20260301142133013"></p><h2 id="数学"><a href="#数学" class="headerlink" title="数学"></a>数学</h2><p>质数判断，开根号法</p><p>数只能被1和它本身整除，而非素数除它们之外，至少还有两个除数a和b。为了判断num是否为非素数，我们可以在[2,num-1]区间查找是否存在num的除数。进一步观察非素数的因子的特征，a和b是相互配对的，且必然有一个比较小，假设较小的数为min，我们只要去搜索[2,min]区间即可。现在问题就转换为如何求min 的最大值了，小数×大数=n，为了让小数尽可能地大，缩小大数，最后变成小数×小数=n，则min的最大值为sqrt(n)</p><p><img data-src="https://files.seeusercontent.com/2026/03/01/Oxd9/image-20260301142318391.png" alt="image-20260301142318391"></p><p>对于素数判断的问题，该算法的时间复杂度还是可以接受的，但对于计数问题，如求解n以内的素数个数，1至n中的每个数都要调用一次函数来验证，时间复杂度将达到O(n$\sqrt{n}$)，耗时过大</p><p><strong>埃氏筛法</strong></p><p>利用质数的倍数是合数的性质，从小到大遍历每个数字，如果数字为质数，则筛掉其倍数。</p><p><strong>线性筛法</strong></p><p>线性筛法（Linear Sieve），通常也被称为<strong>欧拉筛（Euler Sieve）</strong>。它是对埃氏筛（Sieve of Eratosthenes）的极致优化。</p><p>埃氏筛的问题在于：<strong>同一个合数会被多个质因数重复筛去</strong>。例如 $30$，会被 $2$ 筛一次，$3$ 筛一次，$5$ 筛一次。这导致了 $O(n \log \log n)$ 的复杂度。而线性筛的核心思想是：<strong>让每一个合数只被它的“最小质因数”筛掉一次</strong>，从而达到真正的 $O(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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">linearSieve</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">    vector&lt;<span class="type">int</span>&gt; primes;           <span class="comment">// 存储找到的质数</span></span><br><span class="line">    <span class="function">vector&lt;<span class="type">bool</span>&gt; <span class="title">isNotPrime</span><span class="params">(n + <span class="number">1</span>, <span class="literal">false</span>)</span></span>; <span class="comment">// 标记是否为合数</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt;= n; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!isNotPrime[i]) &#123;</span><br><span class="line">            primes.<span class="built_in">push_back</span>(i);  <span class="comment">// i 是质数</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历已找到的质数表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> p : primes) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i * p &gt; n) <span class="keyword">break</span>; <span class="comment">// 越界检查</span></span><br><span class="line">            </span><br><span class="line">            isNotPrime[i * p] = <span class="literal">true</span>; <span class="comment">// 每个合数由其最小质因数筛去</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 重要：精髓所在</span></span><br><span class="line">            <span class="keyword">if</span> (i % p == <span class="number">0</span>) <span class="keyword">break</span>; </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 输出质数个数</span></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Total primes up to &quot;</span> &lt;&lt; n &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; primes.<span class="built_in">size</span>() &lt;&lt; endl;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最大公约数根据欧几里得算法的定理：两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数，我们可以通过递归，不断缩小这两个数字，直到某个数字为0，则另外一个数字为最大公约数</p><p>最小公倍数最大公约数和最小公倍数存在一个性质：两个数的乘积等于这两个数的最大公约数和最小公倍数的乘积，因此，两数最小公倍数=两数乘积 / 两数最大公约数</p><p>KMP：改进的字符串匹配算法。经典的应用场景是查找字符串A在字符串B的起始位置。</p><p>马拉车：处理回文字符串问题的算法。经典的应用场景是查找字符串的最长回文字串。</p><p>树状数组：实现单点修改，区间查询的数据结构。实现难度较低，功能有限。</p><p>线段树：实现区间修改，区间查询的数据结构。实现难度较高，功能强大。</p><h1 id="融会贯通"><a href="#融会贯通" class="headerlink" title="融会贯通"></a>融会贯通</h1><h2 id="循环位移问题"><a href="#循环位移问题" class="headerlink" title="循环位移问题"></a>循环位移问题</h2><p>循环移位是一类非常经典的问题，通常涉及数组、字符串和链表等线性数据结构，包括移动问题、查找问题和包含问题等类型。虽然表现形式有很多，但其问题本质是相似的，只要理解其背后的算法思想，你便能掌握不同数据结构下的实现。</p><p><strong>轮转数组</strong></p><p>给定一个整数数组 <code>nums</code>，将数组中的元素向右轮转 <code>k</code> 个位置，其中 <code>k</code> 是非负数。</p><p>1.空间复杂度为O(n)的解法</p><p>如果采用原地旋转的方法，那么部分数据可能会因为在操作过程中被覆盖而丢失，解决这个问题最简单的做法是在开始时复制一个完全一样的数组，每次移动时从新的数组取数，但是这样占用的额外空间会随着数组大小的变化而线性增加，算法的空间复杂度为O(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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">rotate</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        k = k % nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (k == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; copy_nums = nums;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; nums.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            nums[(i + k) % sz] = copy_nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>2.空间换时间</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">rotate</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        k = k % nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">if</span> (k == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; copy_nums = nums;</span><br><span class="line">       </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            copy_nums.<span class="built_in">push_back</span>(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> start_pos = sz - k;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = start_pos; i &lt; start_pos + sz; i++) &#123;</span><br><span class="line">            nums[i - start_pos] = copy_nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>3.三次翻转法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">rotate</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        k = k % nums.<span class="built_in">size</span>();</span><br><span class="line"></span><br><span class="line">        <span class="built_in">reverse</span>(nums.<span class="built_in">begin</span>(),nums.<span class="built_in">end</span>());</span><br><span class="line">        <span class="built_in">reverse</span>(nums.<span class="built_in">begin</span>(),nums.<span class="built_in">begin</span>()+k);</span><br><span class="line">        <span class="built_in">reverse</span>(nums.<span class="built_in">begin</span>()+k,nums.<span class="built_in">end</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>旋转链表</strong></p><p>给你一个链表的头节点 <code>head</code> ，旋转链表，将链表每个节点向右移动 <code>k</code> 个位置。</p><p>由于 $k$ 可能大于链表的长度 $n$，首先我们需要知道链表的长度。</p><p>算法步骤：</p><ol><li><strong>计算长度 $n$</strong>：遍历链表得到长度 $n$，同时找到当前的尾节点 <code>tail</code>。</li><li><strong>处理 $k$</strong>：实际旋转的步数是 $k \% n$。如果 $k \% n = 0$，说明旋转后还是原样，直接返回 <code>head</code>。</li><li><strong>闭合成环</strong>：将尾节点的 <code>next</code> 指向头节点，使链表变成一个<strong>循环链表</strong>。</li><li><strong>寻找断开点</strong>：向右移动 $k$ 个位置，相当于从头节点向后走 $n - (k \% n) - 1$ 步找到<strong>新的尾节点</strong>。</li><li><strong>断开环</strong>：新的头节点就是 <code>new_tail-&gt;next</code>，然后将 <code>new_tail-&gt;next</code> 设为 <code>nullptr</code>。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">ListNode* <span class="title">rotateRight</span><span class="params">(ListNode* head, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 1. 特判：空链表、单节点或 k=0</span></span><br><span class="line">        <span class="keyword">if</span> (!head || !head-&gt;next || k == <span class="number">0</span>) <span class="keyword">return</span> head;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 计算链表长度 n，并找到尾节点</span></span><br><span class="line">        <span class="type">int</span> n = <span class="number">1</span>;</span><br><span class="line">        ListNode* tail = head;</span><br><span class="line">        <span class="keyword">while</span> (tail-&gt;next) &#123;</span><br><span class="line">            tail = tail-&gt;next;</span><br><span class="line">            n++;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 修正 k</span></span><br><span class="line">        k = k % n;</span><br><span class="line">        <span class="keyword">if</span> (k == <span class="number">0</span>) <span class="keyword">return</span> head;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. 将链表连成环</span></span><br><span class="line">        tail-&gt;next = head;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5. 找到新的尾节点：走 n - k - 1 步</span></span><br><span class="line">        ListNode* new_tail = head;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n - k - <span class="number">1</span>; i++) &#123;</span><br><span class="line">            new_tail = new_tail-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6. 设定新的头节点并断开环</span></span><br><span class="line">        ListNode* new_head = new_tail-&gt;next;</span><br><span class="line">        new_tail-&gt;next = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> new_head;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="编辑距离"><a href="#编辑距离" class="headerlink" title="编辑距离"></a>编辑距离</h2><p>编辑距离算法被数据科学家广泛应用，是用作机器翻译和语音识别评价标准的基本算法。</p><p>最直观的方法是暴力检查所有可能的编辑方法，取最短的一个。所有可能的编辑方法达到指数级，但我们不需要进行这么多计算，因为我们只需要找到距离最短的序列而不是所有可能的序列。</p><p>给你两个单词 <code>word1</code> 和 <code>word2</code>， <em>请返回将 <code>word1</code> 转换成 <code>word2</code> 所使用的最少操作数</em> 。</p><p>你可以对一个单词进行如下三种操作：</p><ul><li>插入一个字符</li><li>删除一个字符</li><li>替换一个字符</li></ul><p>定义 $dp[i][j]$ 为将 <code>word1</code> 的前 $i$ 个字符转换成 <code>word2</code> 的前 $j$ 个字符所需要的最少操作数</p><p>假设我们要计算 $dp[i][j]$：</p><ol><li><p><strong>如果 <code>word1[i-1] == word2[j-1]</code></strong>（当前字符相同）：</p><ul><li>不需要任何操作，操作数等于之前的状态：$dp[i][j] = dp[i-1][j-1]$。</li></ul></li><li><p><strong>如果 <code>word1[i-1] != word2[j-1]</code></strong>（当前字符不同）：</p><p>我们需要从以下三种操作中选一个步数最少的：</p><ul><li><strong>插入</strong>：在 <code>word1</code> 插入一个字符，相当于匹配 <code>word1</code> 的前 $i$ 个和 <code>word2</code> 的前 $j-1$ 个：$dp[i][j-1] + 1$。</li><li><strong>删除</strong>：把 <code>word1</code> 的第 $i$ 个字符删掉，相当于匹配 <code>word1</code> 的前 $i-1$ 个和 <code>word2</code> 的前 $j$ 个：$dp[i-1][j] + 1$。</li><li><strong>替换</strong>：把 <code>word1</code> 的第 $i$ 个字符换成 <code>word2</code> 的第 $j$ 个，相当于匹配 <code>word1</code> 的前 $i-1$ 个和 <code>word2</code> 的前 $j-1$ 个：$dp[i-1][j-1] + 1$。</li></ul></li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minDistance</span><span class="params">(string word1, string word2)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = word<span class="number">1.l</span>ength();</span><br><span class="line">        <span class="type">int</span> n = word<span class="number">2.l</span>ength();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 定义 DP 数组，dp[i][j] 表示 word1[0..i-1] 到 word2[0..j-1] 的距离</span></span><br><span class="line">        <span class="comment">// 多开一行一列处理空字符串的情况</span></span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(m + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n + <span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 边界条件初始化</span></span><br><span class="line">        <span class="comment">// word1 为空，word2 有 j 个字符，需要插入 j 次</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt;= n; j++) dp[<span class="number">0</span>][j] = j;</span><br><span class="line">        <span class="comment">// word2 为空，word1 有 i 个字符，需要删除 i 次</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= m; i++) dp[i][<span class="number">0</span>] = i;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 填充 DP 表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (word1[i - <span class="number">1</span>] == word2[j - <span class="number">1</span>]) &#123;</span><br><span class="line">                    <span class="comment">// 字符相同，继承左上方的值</span></span><br><span class="line">                    dp[i][j] = dp[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 字符不同，取 (插入、删除、替换) 的最小值 + 1</span></span><br><span class="line">                    dp[i][j] = <span class="built_in">min</span>(&#123;dp[i - <span class="number">1</span>][j - <span class="number">1</span>], dp[i - <span class="number">1</span>][j], dp[i][j - <span class="number">1</span>]&#125;) + <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> dp[m][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>编辑距离是自然语言处理（NLP）中拼写检查、DNA 序列比对的基础。<em>进阶：</em> 实际上因为每次计算 $dp[i][j]$ 只依赖于上一行和当前行的前一个元素，我们可以用<strong>滚动数组</strong>将空间复杂度优化到 $O(n)$。</p><p>它的变种非常多，比如：</p><ul><li><strong>最长公共子序列 (LCS)</strong>：其实就是只有删除和插入操作，没有替换操作。</li><li><strong>两个字符串的删除操作</strong>：求最少步数使两个字符串相等。</li></ul><p><strong>最长重复子数组</strong></p><p>给两个整数数组 <code>nums1</code> 和 <code>nums2</code> ，返回 <em>两个数组中 <strong>公共的</strong> 、长度最长的子数组的长度</em> 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findLength</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums1, vector&lt;<span class="type">int</span>&gt;&amp; nums2)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz1 = nums<span class="number">1.</span><span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> sz2 = nums<span class="number">2.</span><span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(sz1 + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(sz2 + <span class="number">1</span>));</span><br><span class="line">        <span class="comment">// 状态转移方程 dp[i][j] = dp[i-1][j-1] &amp;&amp; nums1[i-1] == nums2[j-1]</span></span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= sz1; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= sz2; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (nums1[i - <span class="number">1</span>] == nums2[j - <span class="number">1</span>]) &#123;</span><br><span class="line">                    dp[i][j] = dp[i - <span class="number">1</span>][j - <span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    dp[i][j] = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                res = <span class="built_in">max</span>(res,dp[i][j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="第k问题"><a href="#第k问题" class="headerlink" title="第k问题"></a>第k问题</h2><p>力扣（LeetCode）中有关第k的题目一共有8道，其中包含1道简单级别、5道中等级别和2道困难级别的题目。</p><p>第60题第k个排列，第779题第k个语法符号，属于找规律的题目。</p><p>第215题数组中的第k个最大元素，第703题数据流中的第k大元素，可以用堆来解决。</p><p>第230题二叉搜索树中第k小的元素，这个比较特殊，用到了二叉搜索树这种数据结构，因此可以尝试用二分法解决，也可以使用前序遍历来解决，不过复杂度会更高一点。</p><p>第378题有序矩阵中第k小的元素，这道题可以利用横向/纵向分别有序的性质使用二分法解决。</p><p>第668题乘法表中第k小的数，可以使用二分法解决。</p><p>第k问题往往有3种解法：堆、二分法和找规律</p><p>堆的一个经典的实现是完全二叉树，如果用完全二叉树来实现的话就是二叉堆，当然相应地也有其他种类的堆。堆的任意非叶子节点至少满足以下性质之一。</p><p>●　性质1：nodes[i]&lt;=nodes[2i+1]and nodes[i]&lt;=nodes[2i+2]。</p><p>●　性质2：nodes[i]&gt;=nodes[2i+1]and nodes[i]&gt;=nodes[2i+2]。即任一非叶子节点的值不大于或不小于其左/右孩子节点的值</p><p>堆分为大顶堆和小顶堆，满足性质1的是小顶堆，满足性质2的则是大顶堆。由上述性质可知大顶堆的堆顶的值是最大的，小顶堆的堆顶的值是最小的。堆有两个操作：一个是push，一个是pop。不管哪种操作，都会破坏堆的性质，因此每次操作之后都需要进行调整，使之重新满足堆的性质。</p><p><strong>数组中第k个最大元素</strong></p><p>给定整数数组 <code>nums</code> 和整数 <code>k</code>，请返回数组中第 <code>**k**</code> 个最大的元素。</p><p>请注意，你需要找的是数组排序后的第 <code>k</code> 个最大的元素，而不是第 <code>k</code> 个不同的元素。</p><p>你必须设计并实现时间复杂度为 <code>O(n)</code> 的算法解决此问题。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">partition</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums,<span class="type">int</span> i,<span class="type">int</span> j,<span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(i == j) &#123;</span><br><span class="line">            <span class="keyword">return</span> nums[k];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> left = i<span class="number">-1</span>;</span><br><span class="line">        <span class="type">int</span> right = j<span class="number">+1</span>;</span><br><span class="line">        <span class="type">int</span> pivot = nums[i];</span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">do</span>&#123;</span><br><span class="line">                left++;</span><br><span class="line">            &#125;<span class="keyword">while</span>(nums[left]&lt;pivot);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                right--;</span><br><span class="line">            &#125;<span class="keyword">while</span>(nums[right]&gt;pivot);</span><br><span class="line">            <span class="keyword">if</span>(left&gt;=right) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">swap</span>(nums[left],nums[right]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(k&lt;=right) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">partition</span>(nums,i,right,k);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">partition</span>(nums,<span class="number">1</span>+right,j,k);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findKthLargest</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">partition</span>(nums,<span class="number">0</span>,nums.<span class="built_in">size</span>()<span class="number">-1</span>,nums.<span class="built_in">size</span>()-k);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>也可以建立小根堆,堆大小为k,堆顶元素最小，如果要添加的元素大于堆顶元素且容量超过k,则弹出堆顶元素。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">findKthLargest</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// return partition(nums,0,nums.size()-1,nums.size()-k);</span></span><br><span class="line">        priority_queue&lt;<span class="type">int</span>,vector&lt;<span class="type">int</span>&gt;,greater&lt;<span class="type">int</span>&gt;&gt; q;</span><br><span class="line">        <span class="comment">// 建立一个k大小的小根堆 堆顶元素最小,如果添加的元素大于堆顶 则加入</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; n:nums) &#123;</span><br><span class="line">            <span class="type">int</span> sz = q.<span class="built_in">size</span>();</span><br><span class="line">            <span class="keyword">if</span>(sz&lt;k) &#123;</span><br><span class="line">                q.<span class="built_in">push</span>(n);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="comment">// 比较</span></span><br><span class="line">                <span class="keyword">if</span>(q.<span class="built_in">top</span>()&lt;n) &#123;</span><br><span class="line">                    <span class="comment">// 堆顶元素更小</span></span><br><span class="line">                    q.<span class="built_in">pop</span>();</span><br><span class="line">                    q.<span class="built_in">push</span>(n);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> q.<span class="built_in">top</span>();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>也可以建立一个大根堆，做 <em>k</em>−1 次删除操作后堆顶元素就是我们要找的答案</p><p><strong>数据流中第K大元素</strong></p><p>设计一个找到数据流中第 <code>k</code> 大元素的类（class）。注意是排序后的第 <code>k</code> 大元素，不是第 <code>k</code> 个不同的元素。</p><p>请实现 <code>KthLargest</code> 类：</p><ul><li><code>KthLargest(int k, int[] nums)</code> 使用整数 <code>k</code> 和整数流 <code>nums</code> 初始化对象。</li><li><code>int add(int val)</code> 将 <code>val</code> 插入数据流 <code>nums</code> 后，返回当前数据流中第 <code>k</code> 大的元素。</li></ul><p>可以使用一个大小为 k 的优先队列来存储前 k 大的元素，其中优先队列的队头为队列中最小的元素，也就是第 k 大的元素。</p><p>在单次插入的操作中，我们首先将元素 val 加入到优先队列中。如果此时优先队列的大小大于 k，我们需要将优先队列的队头元素弹出，以保证优先队列的大小为 k。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">KthLargest</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    priority_queue&lt;<span class="type">int</span>, vector&lt;<span class="type">int</span>&gt;, greater&lt;<span class="type">int</span>&gt;&gt; q;</span><br><span class="line">    <span class="type">int</span> k;</span><br><span class="line">    <span class="built_in">KthLargest</span>(<span class="type">int</span> k, vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        <span class="keyword">this</span>-&gt;k = k;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; x: nums) &#123;</span><br><span class="line">            <span class="built_in">add</span>(x);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">add</span><span class="params">(<span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        q.<span class="built_in">push</span>(val);</span><br><span class="line">        <span class="keyword">if</span> (q.<span class="built_in">size</span>() &gt; k) &#123;</span><br><span class="line">            q.<span class="built_in">pop</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> q.<span class="built_in">top</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>二叉搜索树中第K小的元素</strong></p><p>给定一个二叉搜索树的根节点 <code>root</code> ，和一个整数 <code>k</code> ，请你设计一个算法查找其中第 <code>k</code> 小的元素（<code>k</code> 从 1 开始计数）。</p><p>找第 $k$ 小的元素，本质上就是找<strong>中序遍历序列中的第 $k$ 个元素</strong>。</p><p> 核心思路：迭代式中序遍历</p><p>虽然递归实现很简单，但在面试中，<strong>迭代法（使用栈）通常更受青睐。因为迭代法允许我们在找到第 $k$ 个元素时立即停止</strong>，而不需要走完整个遍历过程。</p><p>算法步骤：</p><ol><li><strong>初始化</strong>：准备一个栈 <code>st</code> 和一个当前指针 <code>curr</code> 指向根节点。</li><li><strong>向左深钻</strong>：只要 <code>curr</code> 不为空，就把它压入栈中，并向左子节点移动。这保证了我们总是先处理最小的元素。</li><li><strong>弹出与计数</strong>：当左边走到底了，从栈中弹出一个节点：<ul><li>计数器 $k$ 减 1。</li><li>如果 $k == 0$，当前的节点值就是答案。</li></ul></li><li><strong>向右转向</strong>：将 <code>curr</code> 指向当前弹出节点的右子节点，重复上述过程。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">kthSmallest</span><span class="params">(TreeNode* root, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        stack&lt;TreeNode*&gt; st;</span><br><span class="line">        TreeNode* curr = root;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (curr != <span class="literal">nullptr</span> || !st.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="comment">// 1. 尽可能向左走，把路径上的节点全部压栈</span></span><br><span class="line">            <span class="keyword">while</span> (curr != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">                st.<span class="built_in">push</span>(curr);</span><br><span class="line">                curr = curr-&gt;left;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 2. 弹出栈顶元素（当前剩余节点中的最小值）</span></span><br><span class="line">            curr = st.<span class="built_in">top</span>();</span><br><span class="line">            st.<span class="built_in">pop</span>();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 3. 计数并判断</span></span><br><span class="line">            <span class="keyword">if</span> (--k == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> curr-&gt;val;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 4. 转向右子树</span></span><br><span class="line">            curr = curr-&gt;right;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>; <span class="comment">// 理论上不会走到这里</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果二叉搜索树经常被修改（插入/删除操作）并且你需要频繁地查找第 <code>k</code> 小的值，你将如何优化算法？</p><p>最有效的优化方案是构建一棵<strong>顺序统计树（Order Statistic Tree）</strong>。核心思想是：<strong>空间换时间</strong>。通过在每个节点中维护额外的统计信息，将查找复杂度从 $O(H + k)$ 降低到稳定且高效的 <strong>$O(H)$</strong>（在平衡状态下为 <strong>$O(\log N)$</strong>）。</p><p>在传统的 BST 节点结构中增加一个 <code>size</code> 变量，用于存储<strong>以当前节点为根的子树中的节点总数</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">TreeNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> val;</span><br><span class="line">    <span class="type">int</span> size; <span class="comment">// 新增：记录子树节点总数</span></span><br><span class="line">    TreeNode *left, *right;</span><br><span class="line">    <span class="built_in">TreeNode</span>(<span class="type">int</span> x) : <span class="built_in">val</span>(x), <span class="built_in">size</span>(<span class="number">1</span>), <span class="built_in">left</span>(<span class="literal">nullptr</span>), <span class="built_in">right</span>(<span class="literal">nullptr</span>) &#123;&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol><li>查询逻辑：利用 <code>size</code> 进行“二分剪枝”</li></ol><p>有了 <code>size</code> 字段，我们就不再需要完整的中序遍历，而是可以像二分查找一样直接定位：</p><ul><li>设当前节点为 <code>root</code>，其左子树节点数为 <code>left_size</code>（如果左子树为空则为 0）。</li><li><strong>情况 A：</strong> <code>k == left_size + 1</code><ul><li>说明当前节点正好是第 $k$ 小的元素，直接返回。</li></ul></li><li><strong>情况 B：</strong> <code>k &lt;= left_size</code><ul><li>说明第 $k$ 小的元素一定在左子树中，递归进入左子树查找。</li></ul></li><li><strong>情况 C：</strong> <code>k &gt; left_size + 1</code><ul><li>说明前 <code>left_size + 1</code> 个小的元素（左子树 + 根节点）都不符合要求。</li><li>我们需要去右子树寻找，此时目标变为寻找右子树中的第 <strong><code>k - (left_size + 1)</code></strong> 小元素。</li></ul></li></ul><p><strong>查询复杂度：$O(H)$</strong>（$H$ 为树高）。</p><ol><li>维护逻辑：插入与删除</li></ol><p>由于树会频繁修改，我们必须在每次 <code>insert</code> 或 <code>delete</code> 操作时更新路径上所有节点的 <code>size</code>：</p><ul><li><strong>插入：</strong> 沿着插入路径向下寻找位置时，对路径上的每个节点执行 <code>size++</code>。</li><li><strong>删除：</strong> 沿着删除路径寻找目标节点时，对路径上的每个节点执行 <code>size--</code>。</li></ul><p>如果 BST 退化成链表，$H$ 会变成 $N$，查询效率依然会降到 $O(N)$。为了确保在频繁修改下性能始终稳定在 <strong>$O(\log N)$</strong>，必须配合<strong>平衡二叉搜索树（Balanced BST）</strong>使用：</p><ul><li><strong>AVL 树或红黑树：</strong> 在旋转（Rotation）操作时，除了更新父子关系，还需要重新计算旋转涉及到的两个节点的 <code>size</code>。<ul><li><code>node-&gt;size = get_size(node-&gt;left) + get_size(node-&gt;right) + 1;</code></li></ul></li></ul><p><strong>有序矩阵中第k小的元素</strong></p><p>给定一个n×n的矩阵，其中每行和每列元素均按升序排序，找到矩阵中第k小的元素。请注意，它是排序后的第k小元素，而不是第k个元素</p><p>这种方法的思路不是在“下标”上二分，而是在“数值范围”上二分。矩阵中最小的元素是 <code>matrix[0][0]</code>，最大的是 <code>matrix[n-1][n-1]</code>。</p><p>核心步骤：</p><ol><li><strong>确定值域</strong>：左边界 $left = matrix[0][0]$，右边界 $right = matrix[n-1][n-1]$。</li><li><strong>取中间值</strong>：$mid = left + (right - left) / 2$。</li><li><strong>阶梯计数</strong>：利用矩阵行列有序的特性，在 $O(n)$ 时间内统计矩阵中小于或等于 $mid$ 的元素个数 <code>count</code>。<ul><li>从左下角开始走：如果当前值 $\le mid$，说明这一列上方的元素全符合要求，<code>count += (i + 1)</code>，然后向右移。</li><li>如果当前值 $&gt; mid$，说明当前值太大，向上移。</li></ul></li><li><strong>调整边界</strong>：<ul><li>如果 <code>count &gt;= k</code>，说明第 $k$ 小的数可能就是 $mid$ 或者在 $mid$ 左侧，令 $right = mid$。</li><li>如果 <code>count &lt; k</code>，说明 $mid$ 太小了，令 $left = mid + 1$。</li></ul></li></ol><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><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_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">kthSmallest</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> n = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> left = matrix[<span class="number">0</span>][<span class="number">0</span>];</span><br><span class="line">        <span class="type">int</span> right = matrix[n - <span class="number">1</span>][n - <span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">check</span>(matrix, mid, k, n)) &#123;</span><br><span class="line">                right = mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> left;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 统计矩阵中小于等于 mid 的元素个数，并判断是否 &gt;= k</span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">check</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix, <span class="type">int</span> mid, <span class="type">int</span> k, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> count = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> i = n - <span class="number">1</span>; <span class="comment">// 从左下角开始</span></span><br><span class="line">        <span class="type">int</span> j = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (i &gt;= <span class="number">0</span> &amp;&amp; j &lt; n) &#123;</span><br><span class="line">            <span class="keyword">if</span> (matrix[i][j] &lt;= mid) &#123;</span><br><span class="line">                count += (i + <span class="number">1</span>); <span class="comment">// 当前列 i 以上的都小于 mid</span></span><br><span class="line">                j++;              <span class="comment">// 向右移</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                i--;              <span class="comment">// 向上移</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> count &gt;= k;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>乘法表中第k小的数</strong></p><p>几乎每一个人都用 <a href="https://baike.baidu.com/item/乘法表">乘法表</a>。但是你能在乘法表中快速找到第 <code>k</code> 小的数字吗？</p><p>乘法表是大小为 <code>m x n</code> 的一个整数矩阵，其中 <code>mat[i][j] == i * j</code>（下标从 <strong>1</strong> 开始）。</p><p>给你三个整数 <code>m</code>、<code>n</code> 和 <code>k</code>，请你在大小为 <code>m x n</code> 的乘法表中，找出并返回第 <code>k</code> 小的数字。</p><p>既然我们要找的是第 $k$ 小的数，这个数的取值范围一定在 $[1, m \times n]$ 之间。</p><p>可以在这个值域内进行二分查找。对于每一个中间值 $mid$，去计算：<strong>乘法表中有多少个数小于或等于 $mid$？</strong></p><p>在 $m \times n$ 的乘法表中，第 $i$ 行（$i$ 从 1 开始）的数字序列是：</p><script type="math/tex; mode=display">i \times 1, i \times 2, i \times 3, \dots, i \times n</script><p>如果要找出第 $i$ 行中有多少个数 $\le mid$，只需要满足 $i \times j \le mid$，即 $j \le mid / i$。</p><p>由于一列最多只有 $n$ 个数，所以第 $i$ 行中符合条件的个数为：</p><script type="math/tex; mode=display">\min(n, \lfloor mid / i \rfloor)</script><p>我们只需要遍历 $m$ 行，将每一行的结果累加，就能在 $O(m)$ 的时间内得到整个矩阵中 $\le mid$ 的总数。</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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">check</span><span class="params">(<span class="type">int</span> mid,<span class="type">int</span> k,<span class="type">int</span> m,<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 小于等于mid的元素数量</span></span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="comment">// 每一行小于等于mid的元素</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>;i&lt;=m;i++) &#123;</span><br><span class="line">            cnt += <span class="built_in">min</span>(n,mid/i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt&gt;=k;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">findKthNumber</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 值域二分,并利用乘法表优化</span></span><br><span class="line">        <span class="type">int</span> left = <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> right = m*n;</span><br><span class="line">        <span class="keyword">while</span>(left&lt;right) &#123;</span><br><span class="line">            <span class="type">int</span> mid = (right-left)/<span class="number">2</span>+left;</span><br><span class="line">            <span class="keyword">if</span>(<span class="built_in">check</span>(mid,k,m,n)) &#123;</span><br><span class="line">                <span class="comment">// 如果满足</span></span><br><span class="line">                right = mid;</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                left = mid<span class="number">+1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> right;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="计算器"><a href="#计算器" class="headerlink" title="计算器"></a>计算器</h2><p>150逆波兰表达式求值</p><p>给你一个字符串数组 <code>tokens</code> ，表示一个根据 <a href="https://baike.baidu.com/item/逆波兰式/128437">逆波兰表示法</a> 表示的算术表达式。</p><p>请你计算该表达式。返回一个表示表达式值的整数。</p><p><strong>注意：</strong></p><ul><li>有效的算符为 <code>&#39;+&#39;</code>、<code>&#39;-&#39;</code>、<code>&#39;*&#39;</code> 和 <code>&#39;/&#39;</code> 。</li><li>每个操作数（运算对象）都可以是一个整数或者另一个表达式。</li><li>两个整数之间的除法总是 <strong>向零截断</strong> 。</li><li>表达式中不含除零运算。</li><li>输入是一个根据逆波兰表示法表示的算术表达式。</li><li>答案及所有中间计算结果可以用 <strong>32 位</strong> 整数表示。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">evalRPN</span><span class="params">(vector&lt;string&gt;&amp; tokens)</span> </span>&#123;</span><br><span class="line">        stack&lt;<span class="type">int</span>&gt; nums;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tokens.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="comment">// 遇到数字</span></span><br><span class="line">            <span class="keyword">auto</span> t = tokens[i];</span><br><span class="line">            <span class="keyword">if</span> ((t.<span class="built_in">size</span>()&gt;<span class="number">1</span> &amp;&amp; t[<span class="number">0</span>] == <span class="string">&#x27;-&#x27;</span>) || <span class="built_in">isdigit</span>(t[<span class="number">0</span>])) &#123;</span><br><span class="line">                nums.<span class="built_in">push</span>(<span class="built_in">stoi</span>(t));</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 遇到运算符</span></span><br><span class="line">            <span class="comment">// 取出栈中的两个数字进行计算</span></span><br><span class="line">            <span class="type">int</span> val1 = nums.<span class="built_in">top</span>();</span><br><span class="line">            nums.<span class="built_in">pop</span>();</span><br><span class="line">            <span class="type">int</span> val2 = nums.<span class="built_in">top</span>();</span><br><span class="line">            nums.<span class="built_in">pop</span>();</span><br><span class="line">            <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">            <span class="keyword">if</span> (t== <span class="string">&quot;+&quot;</span>) &#123;</span><br><span class="line">                res = val1 + val2;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (t== <span class="string">&quot;-&quot;</span>) &#123;</span><br><span class="line">                res = val2 - val1;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (t == <span class="string">&quot;*&quot;</span>) &#123;</span><br><span class="line">                res = val2 * val1;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                res = val2 / val1;</span><br><span class="line">            &#125;</span><br><span class="line">            nums.<span class="built_in">push</span>(res);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nums.<span class="built_in">top</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>241为运算表达式设计优先级</p><p>给你一个由数字和运算符组成的字符串 <code>expression</code> ，按不同优先级组合数字和运算符，计算并返回所有可能组合的结果。你可以 <strong>按任意顺序</strong> 返回答案。</p><p>生成的测试用例满足其对应输出值符合 32 位整数范围，不同结果的数量不超过 <code>104</code> 。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    unordered_map&lt;string, vector&lt;<span class="type">int</span>&gt;&gt; memo;</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">diffWaysToCompute</span><span class="params">(string expression)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 分治 递归</span></span><br><span class="line">        <span class="comment">// 记忆化搜索</span></span><br><span class="line">        <span class="keyword">if</span> (expression.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (memo.<span class="built_in">count</span>(expression)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo[expression];</span><br><span class="line">        &#125;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; expression.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            <span class="type">char</span>&amp; ch = expression[i];</span><br><span class="line">            <span class="keyword">if</span> (!<span class="built_in">isdigit</span>(ch)) &#123;</span><br><span class="line">                <span class="comment">// 是运算符</span></span><br><span class="line">                <span class="comment">// 分治</span></span><br><span class="line">                string leftExpression = expression.<span class="built_in">substr</span>(<span class="number">0</span>, i);</span><br><span class="line">                string rightExpression = expression.<span class="built_in">substr</span>(i + <span class="number">1</span>);</span><br><span class="line">                vector&lt;<span class="type">int</span>&gt; leftAns = <span class="built_in">diffWaysToCompute</span>(leftExpression);</span><br><span class="line">                vector&lt;<span class="type">int</span>&gt; rightAns = <span class="built_in">diffWaysToCompute</span>(rightExpression);</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; val1 : leftAns) &#123;</span><br><span class="line">                    <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; val2 : rightAns) &#123;</span><br><span class="line">                        <span class="type">int</span> r&#123;&#125;;</span><br><span class="line">                        <span class="keyword">if</span> (ch == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">                            r = val1 + val2;</span><br><span class="line">                        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;-&#x27;</span>) &#123;</span><br><span class="line">                            r = val1 - val2;</span><br><span class="line">                        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                            r = val1 * val2;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            r = val1 / val2;</span><br><span class="line">                        &#125;</span><br><span class="line">                        res.<span class="built_in">push_back</span>(r);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (res.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="comment">// 只有一个数字</span></span><br><span class="line">            <span class="type">int</span> r = <span class="built_in">stoi</span>(expression);</span><br><span class="line">            res.<span class="built_in">push_back</span>(r);</span><br><span class="line">        &#125;</span><br><span class="line">        memo[expression] = res;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>224<strong>计算器</strong></p><p>给你一个字符串表达式 <code>s</code> ，请你实现一个基本计算器来计算并返回它的值。</p><p>注意:不允许使用任何将字符串作为数学表达式计算的内置函数，比如 <code>eval()</code> 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">calculate</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> sign = <span class="number">1</span>; <span class="comment">// 表示当前式子正负</span></span><br><span class="line">        stack&lt;<span class="type">int</span>&gt; ops;</span><br><span class="line">        ops.<span class="built_in">push</span>(<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">char</span>&amp; ch = s[i];</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isdigit</span>(ch)) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="type">long</span> num&#123;&#125;;</span><br><span class="line">                <span class="keyword">while</span> (i &lt; sz &amp;&amp; <span class="built_in">isdigit</span>(s[i])) &#123;</span><br><span class="line">                    num = num * <span class="number">10</span> + s[i] - <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">                    i++;</span><br><span class="line">                &#125;</span><br><span class="line">                res += sign * num;</span><br><span class="line">                i--;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">                sign = ops.<span class="built_in">top</span>();</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;-&#x27;</span>) &#123;</span><br><span class="line">                sign = -ops.<span class="built_in">top</span>();</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;(&#x27;</span>) &#123;</span><br><span class="line">                ops.<span class="built_in">push</span>(sign);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;)&#x27;</span>) &#123;</span><br><span class="line">                ops.<span class="built_in">pop</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>227基本计算器<strong>II</strong></p><p>给你一个字符串表达式 <code>s</code> ，请你实现一个基本计算器来计算并返回它的值。</p><p>整数除法仅保留整数部分。</p><p>你可以假设给定的表达式总是有效的。所有中间结果将在 <code>[-231, 231 - 1]</code> 的范围内。</p><p><strong>注意：</strong>不允许使用任何将字符串作为数学表达式计算的内置函数，比如 <code>eval()</code> 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">calculate</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 包含加减乘除 不包含括号</span></span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> num&#123;&#125;;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; nums;</span><br><span class="line">        <span class="type">char</span> preSign = <span class="string">&#x27;+&#x27;</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">char</span>&amp; ch = s[i];</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isdigit</span>(ch)) &#123;</span><br><span class="line">                num = num * <span class="number">10</span> + (ch - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> ((!<span class="built_in">isdigit</span>(ch) &amp;&amp; ch != <span class="string">&#x27; &#x27;</span>) || i == sz - <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果是运算符或者到了最后一位</span></span><br><span class="line">                <span class="keyword">if</span> (preSign == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">                    nums.<span class="built_in">push_back</span>(num);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (preSign == <span class="string">&#x27;-&#x27;</span>) &#123;</span><br><span class="line">                    nums.<span class="built_in">push_back</span>(-num);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (preSign == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                    nums.<span class="built_in">back</span>() *= num;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    nums.<span class="built_in">back</span>() /= num;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 设置运算符和值</span></span><br><span class="line">                preSign = ch;</span><br><span class="line">                num = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">accumulate</span>(nums.<span class="built_in">begin</span>(), nums.<span class="built_in">end</span>(), <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>772<strong>基本计算器</strong></p><p>实现一个基本的计算器来计算简单的表达式字符串。</p><p>表达式字符串只包含非负整数，算符 <code>+</code>、<code>-</code>、<code>*</code>、<code>/</code> ，左括号 <code>(</code> 和右括号 <code>)</code> 。整数除法需要 <strong>向下截断</strong> 。你可以假定给定的表达式总是有效的。所有的中间结果的范围均满足 <code>[-231, 231 - 1]</code> 。</p><p><strong>注意：</strong>你不能使用任何将字符串作为表达式求值的内置函数，比如 <code>eval()</code> 。</p><p>最经典且稳健的方法是使用 <strong>双栈法</strong>：一个栈用于存储数字（<code>nums</code>），另一个栈用于存储操作符（<code>ops</code>）。</p><p><strong>核心逻辑</strong></p><p>为了处理运算符的优先级（比如乘除优于加减，括号优先级最高），我们需要定义一套规则：</p><ol><li><strong>优先级定义</strong>：给运算符设定等级，加减为 1，乘除为 2。</li><li><strong>括号处理</strong>：遇到左括号 <code>(</code> 直接入栈；遇到右括号 <code>)</code> 则不断计算，直到弹出左括号。</li><li><strong>计算时机</strong>：当准备入栈的操作符优先级 <strong>小于或等于</strong> 栈顶操作符时，说明栈顶的运算可以执行了。</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">compute</span><span class="params">(stack&lt;<span class="type">int</span>&gt;&amp; nums, stack&lt;<span class="type">char</span>&gt;&amp; ops)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (nums.<span class="built_in">size</span>() &lt; <span class="number">2</span> || ops.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">char</span>&amp; op = ops.<span class="built_in">top</span>();</span><br><span class="line">        ops.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="type">int</span> b = nums.<span class="built_in">top</span>();</span><br><span class="line">        nums.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="type">int</span> a = nums.<span class="built_in">top</span>();</span><br><span class="line">        nums.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="keyword">if</span> (op == <span class="string">&#x27;+&#x27;</span>) &#123;</span><br><span class="line">            nums.<span class="built_in">push</span>(a + b);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (op == <span class="string">&#x27;-&#x27;</span>) &#123;</span><br><span class="line">            nums.<span class="built_in">push</span>(a - b);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (op == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">            nums.<span class="built_in">push</span>(a * b);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            nums.<span class="built_in">push</span>(a / b);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    unordered_map&lt;<span class="type">char</span>, <span class="type">int</span>&gt; order = &#123;</span><br><span class="line">        &#123;<span class="string">&#x27;+&#x27;</span>, <span class="number">1</span>&#125;,</span><br><span class="line">        &#123;<span class="string">&#x27;-&#x27;</span>, <span class="number">1</span>&#125;,</span><br><span class="line">        &#123;<span class="string">&#x27;*&#x27;</span>, <span class="number">2</span>&#125;,</span><br><span class="line">        &#123;<span class="string">&#x27;/&#x27;</span>, <span class="number">2</span>&#125;,</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">calculate</span><span class="params">(string s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 包含加减乘除 和 括号</span></span><br><span class="line">        stack&lt;<span class="type">int</span>&gt; nums;</span><br><span class="line">        stack&lt;<span class="type">char</span>&gt; ops;</span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">char</span>&amp; ch = s[i];</span><br><span class="line">            <span class="keyword">if</span> (ch == <span class="string">&#x27; &#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isdigit</span>(ch)) &#123;</span><br><span class="line">                <span class="type">int</span> num&#123;&#125;;</span><br><span class="line">                <span class="keyword">while</span> (i &lt; sz &amp;&amp; <span class="built_in">isdigit</span>(s[i])) &#123;</span><br><span class="line">                    num = num * <span class="number">10</span> + (s[i] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">                    i++;</span><br><span class="line">                &#125;</span><br><span class="line">                i--;</span><br><span class="line">                nums.<span class="built_in">push</span>(num);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;(&#x27;</span>) &#123;</span><br><span class="line">                ops.<span class="built_in">push</span>(ch);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ch == <span class="string">&#x27;)&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">while</span> (!ops.<span class="built_in">empty</span>() &amp;&amp; ops.<span class="built_in">top</span>() != <span class="string">&#x27;(&#x27;</span>) &#123;</span><br><span class="line">                    <span class="built_in">compute</span>(nums, ops);</span><br><span class="line">                &#125;</span><br><span class="line">                ops.<span class="built_in">pop</span>();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// (-5+xxx)</span></span><br><span class="line">                <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; s[i - <span class="number">1</span>] == <span class="string">&#x27;(&#x27;</span>) &#123;</span><br><span class="line">                    nums.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 遇到优先级更低的</span></span><br><span class="line">                <span class="keyword">while</span> (!ops.<span class="built_in">empty</span>() &amp;&amp; ops.<span class="built_in">top</span>() != <span class="string">&#x27;(&#x27;</span> &amp;&amp;</span><br><span class="line">                       order[ops.<span class="built_in">top</span>()] &gt;= order[ch]) &#123;</span><br><span class="line">                    <span class="built_in">compute</span>(nums, ops);</span><br><span class="line">                &#125;</span><br><span class="line">                ops.<span class="built_in">push</span>(ch);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (!ops.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="built_in">compute</span>(nums, ops);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nums.<span class="built_in">top</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="解题技巧"><a href="#解题技巧" class="headerlink" title="解题技巧"></a>解题技巧</h1><h2 id="限制条件"><a href="#限制条件" class="headerlink" title="限制条件"></a>限制条件</h2><p>很多时候限制条件也起到提示的作用。</p><p>数据规模</p><p><img data-src="https://files.seeusercontent.com/2026/03/01/Fq6b/image-20260301212937661.png" alt="image-20260301212937661" style="zoom:67%;" /></p><p>除了数据规模，还有一个比较重要的信息是时间复杂度的限制。比如题目要求时间复杂度是对数复杂度，我们就很容易想到二分查找。又如题目要求常数的空间复杂度，我们很容易想到原地算法</p><p><a href="https://leetcode.cn/problems/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix/">转化为全零矩阵的最少反转次数</a></p><p>由于题目给出的矩阵规模非常小（$m, n \le 3$），总格子数最多只有 $9$ 个，这意味着矩阵的总状态数只有 $2^9 = 512$ 种。对于这种状态空间极小且要求“最少操作次数”的问题，<strong>广度优先搜索（BFS）</strong> 是最理想的解法。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minFlips</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; mat)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = mat.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = mat[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 状态压缩</span></span><br><span class="line">        <span class="type">int</span> startState&#123;&#125;;</span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (mat[i][j]) &#123;</span><br><span class="line">                    startState |= (<span class="number">1</span> &lt;&lt; (i * n + j));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 队列</span></span><br><span class="line">        queue&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; q;</span><br><span class="line">        unordered_set&lt;<span class="type">int</span>&gt; vis;</span><br><span class="line">        q.<span class="built_in">push</span>(&#123;startState, <span class="number">0</span>&#125;); <span class="comment">// 状态 步数</span></span><br><span class="line">        vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; points = &#123;</span><br><span class="line">            &#123;<span class="number">0</span>, <span class="number">0</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;&#125;;</span><br><span class="line">        vis.<span class="built_in">insert</span>(startState);</span><br><span class="line">        <span class="keyword">while</span> (!q.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">auto</span> [s, step] = q.<span class="built_in">front</span>();</span><br><span class="line">            q.<span class="built_in">pop</span>();</span><br><span class="line">            <span class="keyword">if</span> (s == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> step;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                    <span class="comment">// 翻转每个格子</span></span><br><span class="line">                    <span class="type">int</span> next_state = s;</span><br><span class="line">                    <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : points) &#123;</span><br><span class="line">                        <span class="type">int</span> nr = i + dir.first;</span><br><span class="line">                        <span class="type">int</span> nc = j + dir.second;</span><br><span class="line">                        <span class="keyword">if</span> (nr &lt; <span class="number">0</span> || nr &gt;= m || nc &lt; <span class="number">0</span> || nc &gt;= n) &#123;</span><br><span class="line">                            <span class="keyword">continue</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">// 异或</span></span><br><span class="line">                        next_state ^= (<span class="number">1</span> &lt;&lt; (nr * n + nc));</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (!vis.<span class="built_in">count</span>(next_state)) &#123;</span><br><span class="line">                        vis.<span class="built_in">insert</span>(next_state);</span><br><span class="line">                        q.<span class="built_in">push</span>(&#123;next_state, step + <span class="number">1</span>&#125;);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a href="https://leetcode.cn/problems/set-matrix-zeroes/">矩阵置零</a></p><p>可以使用用两个标记数组分别记录每一行和每一列是否有零出现。</p><p>具体地，我们首先遍历该数组一次，如果某个元素为 0，那么就将该元素所在的行和列所对应标记数组的位置置为 true。最后我们再次遍历该数组，用标记数组更新原数组即可。</p><p>此外，为了O(1)的空间复杂度.可以用矩阵的第一行和第一列代替两个标记数组。但这样会导致原数组的第一行和第一列被修改，无法记录它们是否原本包含 0。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0。</p><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setZeroes</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">bool</span> flag_row&#123;&#125;, flag_col&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (matrix[i][<span class="number">0</span>] == <span class="number">0</span>) &#123;</span><br><span class="line">                flag_col = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (matrix[<span class="number">0</span>][i] == <span class="number">0</span>) &#123;</span><br><span class="line">                flag_row = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 使用第一行的值 表示哪些列应该为0</span></span><br><span class="line">        <span class="comment">// 使用第一列的值 表示哪些行应该为0</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matrix[i][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                    matrix[i][<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">                    matrix[<span class="number">0</span>][j] = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j&lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matrix[<span class="number">0</span>][j] == <span class="number">0</span> || matrix[i][<span class="number">0</span>] == <span class="number">0</span>) &#123;</span><br><span class="line">                    matrix[i][j] = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (flag_row) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">                matrix[<span class="number">0</span>][i] = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (flag_col) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">                matrix[i][<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="预处理"><a href="#预处理" class="headerlink" title="预处理"></a>预处理</h2><p><strong>顺次数</strong></p><p>我们定义「顺次数」为：每一位上的数字都比前一位上的数字大 <code>1</code> 的整数。</p><p>请你返回由 <code>[low, high]</code> 范围内所有顺次数组成的 <strong>有序</strong> 列表（从小到大排序）。</p><p>核心突破点在于：<strong>顺次数的数量非常有限</strong>。</p><p>由于每一位都比前一位大 $1$，一旦确定了数字的<strong>长度</strong>和<strong>起始数字</strong>，这个顺次数就是唯一的。例如，长度为 $3$ 且以 $1$ 开头的顺次数只能是 $123$。</p><p> 核心思路：滑动窗口 / 枚举</p><p>所有可能的顺次数其实都源自一个基础字符串：<code>&quot;123456789&quot;</code>。</p><p>算法步骤：</p><ol><li><strong>构造母串</strong>：定义字符串 <code>s = &quot;123456789&quot;</code>。</li><li><strong>枚举长度</strong>：顺次数的长度至少为 $2$（题目要求 $low \ge 10$），至多为 $9$。</li><li><strong>滑动窗口截取</strong>：<ul><li>在母串上移动一个固定长度的窗口。</li><li>将截取的子串转换为整数。</li><li>判断该整数是否在 $[low, high]$ 范围内。</li></ul></li><li><strong>排序输出</strong>：由于我们是按长度从小到大、起始位置从左到右枚举的，生成的顺次数天然就是有序的。</li></ol><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><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><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sequentialDigits</span><span class="params">(<span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; ans;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">9</span>; ++i) &#123;</span><br><span class="line">            <span class="type">int</span> num = i;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = i + <span class="number">1</span>; j &lt;= <span class="number">9</span>; ++j) &#123;</span><br><span class="line">                num = num * <span class="number">10</span> + j;</span><br><span class="line">                <span class="keyword">if</span> (num &gt;= low &amp;&amp; num &lt;= high) &#123;</span><br><span class="line">                    ans.<span class="built_in">push_back</span>(num);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">sort</span>(ans.<span class="built_in">begin</span>(), ans.<span class="built_in">end</span>());</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">sequentialDigits</span><span class="params">(<span class="type">int</span> low, <span class="type">int</span> high)</span> </span>&#123;</span><br><span class="line">        vector&lt;<span class="type">int</span>&gt; res;</span><br><span class="line">        string s = <span class="string">&quot;123456789&quot;</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> len = <span class="number">2</span>;len&lt;=<span class="number">9</span>;len++) &#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> start = <span class="number">0</span>;start&lt;=<span class="number">9</span>-len;start++) &#123;</span><br><span class="line">                string r = s.<span class="built_in">substr</span>(start,len);</span><br><span class="line">                <span class="type">int</span> result = <span class="built_in">stoi</span>(r);</span><br><span class="line">                <span class="keyword">if</span>(result&gt;=low &amp;&amp; result&lt;=high) &#123;</span><br><span class="line">                    res.<span class="built_in">push_back</span>(result);</span><br><span class="line">                &#125;<span class="keyword">else</span> <span class="keyword">if</span>(result&gt;high) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>单词接龙</strong></p><p>给定两个单词（beginWord和endWord）和一个字典，找到从beginWord到endWord 的最短转换序列的长度。转换需遵循如下规则。每次转换只能改变一个字母；转换过程中的中间单词必须是字典中的单词。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">ladderLength</span><span class="params">(string beginWord, string endWord,</span></span></span><br><span class="line"><span class="params"><span class="function">                     vector&lt;string&gt;&amp; wordList)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 替换每个单词每个位置的词看是否在词表中</span></span><br><span class="line">        <span class="function">unordered_set&lt;string&gt; <span class="title">wordSet</span><span class="params">(wordList.begin(), wordList.end())</span></span>;</span><br><span class="line">        <span class="keyword">if</span> (!wordSet.<span class="built_in">count</span>(endWord)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// BFS</span></span><br><span class="line">        queue&lt;pair&lt;string, <span class="type">int</span>&gt;&gt; q;</span><br><span class="line">        <span class="comment">// unordered_set&lt;string&gt; vis;</span></span><br><span class="line">        q.<span class="built_in">push</span>(&#123;beginWord, <span class="number">1</span>&#125;);</span><br><span class="line">        <span class="comment">// vis.insert(beginWord);</span></span><br><span class="line">        <span class="keyword">while</span> (!q.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">            <span class="keyword">auto</span> [word, step] = q.<span class="built_in">front</span>();</span><br><span class="line">            q.<span class="built_in">pop</span>();</span><br><span class="line">            <span class="keyword">if</span> (word == endWord) &#123;</span><br><span class="line">                <span class="keyword">return</span> step;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 替换</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; word.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">                string next_word = word;</span><br><span class="line">                <span class="comment">// 对于每个位置的单词</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">char</span> w = <span class="string">&#x27;a&#x27;</span>; w &lt;= <span class="string">&#x27;z&#x27;</span>; w++) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (word[i] != w) &#123;</span><br><span class="line">                        <span class="comment">// 如果字符不相等，替换</span></span><br><span class="line">                        next_word[i] = w;</span><br><span class="line">                        <span class="keyword">if</span> (wordSet.<span class="built_in">count</span>(next_word)) &#123;</span><br><span class="line">                            <span class="comment">// vis.insert(next_word);</span></span><br><span class="line">                            q.<span class="built_in">push</span>(&#123;next_word, step + <span class="number">1</span>&#125;);</span><br><span class="line">                            wordSet.<span class="built_in">erase</span>(next_word);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="不要忽视暴力法"><a href="#不要忽视暴力法" class="headerlink" title="不要忽视暴力法"></a>不要忽视暴力法</h2><p>暴力法可以给你提示，打开思路。暴力法可以给你相对直观的思路，当想到用暴力法解决时，你应该思考暴力法的瓶颈在哪里。比如经典的爬楼梯问题，如果采用暴力法，我们发现中间会有很多重复的运算，这里可以考虑使用哈希表将全部计算存起来从而避免重复计算，得到可用的解。</p><p>暴力法可以通过剪枝、预处理等方法减少程序的运行时间，进而通过所有的测试用例。暴力法之所以有问题，一方面是因为它产生重复的计算；另一方面暴力法往往还会去执行一些根本不可能是结果的代码。在这种情况下，上面的方案就变得不可行，可以通过剪枝来解决这个问题。剪枝在回溯法中应用得比较多。另一种可行的方法是数据预处理</p><p><a href="https://leetcode.cn/problems/count-square-submatrices-with-all-ones/">统计全为 1 的正方形子矩阵</a></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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">countSquares</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// dp[i][j]以matrix[i][j]为右下角 且完全由1组成的正方形的最大边长</span></span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(m, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(n));</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="comment">// 状态转移方程</span></span><br><span class="line">        <span class="comment">// dp[i][j] = 1+min(dp[i-1][j-1] ,dp[i-1][j],dp[i][j-1])  matrix[i][j]</span></span><br><span class="line">        <span class="comment">// == 1</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matrix[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (i == <span class="number">0</span> || j == <span class="number">0</span>) &#123;</span><br><span class="line">                        dp[i][j] = <span class="number">1</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        dp[i][j] = <span class="built_in">min</span>(dp[i][j - <span class="number">1</span>],</span><br><span class="line">                                       <span class="built_in">min</span>(dp[i - <span class="number">1</span>][j], dp[i - <span class="number">1</span>][j - <span class="number">1</span>])) +</span><br><span class="line">                                   <span class="number">1</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    res += dp[i][j];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a href="https://leetcode.cn/problems/maximal-square/">最大正方形</a></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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maximalSquare</span><span class="params">(vector&lt;vector&lt;<span class="type">char</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (matrix.<span class="built_in">size</span>() == <span class="number">0</span> || matrix[<span class="number">0</span>].<span class="built_in">size</span>() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> maxSide = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> rows = matrix.<span class="built_in">size</span>(), columns = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; <span class="built_in">dp</span>(rows, <span class="built_in">vector</span>&lt;<span class="type">int</span>&gt;(columns));</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; columns; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matrix[i][j] == <span class="string">&#x27;1&#x27;</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (i == <span class="number">0</span> || j == <span class="number">0</span>) &#123;</span><br><span class="line">                        dp[i][j] = <span class="number">1</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        dp[i][j] = <span class="built_in">min</span>(<span class="built_in">min</span>(dp[i - <span class="number">1</span>][j], dp[i][j - <span class="number">1</span>]), dp[i - <span class="number">1</span>][j - <span class="number">1</span>]) + <span class="number">1</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    maxSide = <span class="built_in">max</span>(maxSide, dp[i][j]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> maxSquare = maxSide * maxSide;</span><br><span class="line">        <span class="keyword">return</span> maxSquare;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>子串的最大出现次数</strong></p><p>给你一个字符串 <code>s</code> ，请你返回满足以下条件且出现次数最大的 <strong>任意</strong> 子串的出现次数：</p><ul><li>子串中不同字母的数目必须小于等于 <code>maxLetters</code> 。</li><li>子串的长度必须大于等于 <code>minSize</code> 且小于等于 <code>maxSize</code> 。</li></ul><p>题目要求子串长度在 <code>[minSize, maxSize]</code> 之间。但实际上，<strong>我们只需要统计长度正好为 <code>minSize</code> 的子串。</strong></p><p>假设一个长度为 <code>maxSize</code> 的子串出现了 $k$ 次，那么它内部包含的长度为 <code>minSize</code> 的前缀子串也<strong>一定至少出现了 $k$ 次</strong>。 同时，短子串包含的不同字母个数只会比长子串少或相等，因此更容易满足 <code>maxLetters</code> 的限制。 所以，出现次数最多的合法子串，一定可以在长度为 <code>minSize</code> 的子串中找到。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(string&amp; subs, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 统计出现不同字母字数</span></span><br><span class="line">        <span class="type">int</span> charCount[<span class="number">26</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">        <span class="type">int</span> cnt&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; ch : subs) &#123;</span><br><span class="line">            charCount[ch - <span class="string">&#x27;a&#x27;</span>]++;</span><br><span class="line">            <span class="keyword">if</span> (charCount[ch - <span class="string">&#x27;a&#x27;</span>] == <span class="number">1</span>) &#123;</span><br><span class="line">                cnt++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cnt &lt;= k;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maxFreq</span><span class="params">(string s, <span class="type">int</span> maxLetters, <span class="type">int</span> minSize, <span class="type">int</span> maxSize)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 窗口固定为minSize</span></span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        unordered_map&lt;string, <span class="type">int</span>&gt; counts; <span class="comment">// 记录子串出现的频率</span></span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz - minSize + <span class="number">1</span>; i++) &#123;</span><br><span class="line">            string subs = s.<span class="built_in">substr</span>(i, minSize);</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(subs, maxLetters)) &#123;</span><br><span class="line">                counts[subs]++;</span><br><span class="line">                res = <span class="built_in">max</span>(res, counts[subs]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="降维与状态压缩"><a href="#降维与状态压缩" class="headerlink" title="降维与状态压缩"></a>降维与状态压缩</h2><p>状态压缩同样是一个很常见的算法，如“如何将 IP 地址用4个字节来表示等。在实际做题的过程中，很多时候并不会直接要求你进行状态压缩。更多的情况是若不进行状态压缩，则很可能发生内存溢出。为了减少内存占用，避免内存溢出，我们不得不使用状态压缩的技巧。</p><p>假如我们需要判断一个字符串中的字符是否全部唯一。简单起见，假设字符串仅包含小写英文字母。一个简单的思路是进行一次遍历并用集合记录出现过的字母。如果加入集合前发现集合中已经存在了当前的字母则直接返回False.假如我们需要判断一个字符串中的字符是否全部唯一。简单起见，假设字符串仅包含小写英文字母。一个简单的思路是进行一次遍历并用集合记录出现过的字母。如果加入集合前发现集合中已经存在了当前的字母则直接返回False.</p><p>由于字符串只包含小写字母，因此也可以使用一个长度为26的数组来统计每个字母出现的次数，如果某个字母出现次数大于1则直接返回False，但是这种做法并没有在空间上带来优化。下面我们使用状态压缩技巧来进行优化。仔细考虑上面的数组思路会发现，我们只关心某一个字母出现次数是否大于1，因此如果某个字母出现次数等于1，并且又出现了一个同样的字母，直接返回False 即可。也就是说，这里的数组的值只需要用一个二进制位保存即可，即0和1。</p><p>对于这种二值性的存储问题，都可以用位运算来压缩存储状态。只不过有些题目没有必要使用压缩也可以通过，有些题目则必须使用压缩。如果题目中的数据范围在32以内，需要存储的状态很多且具有二值性，那就可以考虑使用状态压缩。由于只有26个字母，因此使用一个int（32位）存储是足够的，这也是前面提到的数据范围在32以内的原因之一。只不过这里的26并不是题目显性给出的，而是一个需要大家自己发掘的条件。关于上面使用集合的那个算法，我们使用了集合的两个操作，分别是将元素添加到集合中和判断元素是否存在于集合中。这里使用位运算来实现这两个操作，用int来代替集合即可</p><p><strong>生命游戏</strong></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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">const</span> vector&lt;pair&lt;<span class="type">int</span>, <span class="type">int</span>&gt;&gt; dirs = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;,  &#123;<span class="number">-1</span>, <span class="number">0</span>&#125;,</span><br><span class="line">                                         &#123;<span class="number">1</span>, <span class="number">1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">-1</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">1</span>&#125;, &#123;<span class="number">-1</span>, <span class="number">-1</span>&#125;&#125;;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">gameOfLife</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; board)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> m = board.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = board[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="comment">// 原地修改</span></span><br><span class="line">                <span class="type">int</span> neighbors&#123;&#125;;</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; dir : dirs) &#123;</span><br><span class="line">                    <span class="type">int</span> nx = i + dir.first;</span><br><span class="line">                    <span class="type">int</span> ny = j + dir.second;</span><br><span class="line">                    <span class="comment">// 在边界内</span></span><br><span class="line">                    <span class="keyword">if</span> (nx &lt; <span class="number">0</span> || nx &gt;= m || ny &lt; <span class="number">0</span> || ny &gt;= n) &#123;</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (board[nx][ny] == <span class="number">1</span> || board[nx][ny] == <span class="number">3</span>) &#123;</span><br><span class="line">                        neighbors++;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 0 死-&gt;死</span></span><br><span class="line">                <span class="comment">// 1 活-&gt;活</span></span><br><span class="line">                <span class="comment">// 2 死-&gt;活</span></span><br><span class="line">                <span class="comment">// 3 活-&gt;死</span></span><br><span class="line">                <span class="keyword">if</span> (neighbors &lt; <span class="number">2</span> &amp;&amp; board[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    board[i][j] = <span class="number">3</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (neighbors &gt; <span class="number">3</span> &amp;&amp; board[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    board[i][j] = <span class="number">3</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (neighbors == <span class="number">3</span> &amp;&amp; board[i][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                    board[i][j] = <span class="number">2</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 遍历</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (board[i][j] == <span class="number">2</span>) &#123;</span><br><span class="line">                    board[i][j] = <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (board[i][j] == <span class="number">3</span>) &#123;</span><br><span class="line">                    board[i][j] = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>降维和状态压缩类似，降维指的是将高维的数据转化为低维的数据。比如将一个二维数组转化为一维的字符串，这种技巧在BFS中用的比较多。在BFS中，我们首先要明确的是开始状态和结束状态，而如果这个状态是一个二维数组，在通常情况下都需要对其进行降维处理，甚至降维之后还要再使用上面提到的状态压缩技巧</p><h2 id="猜测tag"><a href="#猜测tag" class="headerlink" title="猜测tag"></a>猜测tag</h2><p>题目信息中会有很多关键词，这些关键词对于猜测tag至关重要。比如题目中出现连续子串、连续子数组，那么就可能使用滑动窗口。如果题目中出现有序，就有可能使用双指针。除此之外，复杂度也是一个很好的提示。O(1)空间复杂度意味着可能要使用原地算法，就有可能需要进行数据压缩。如果时间复杂度要求O(logn)就有可能需要二分法。比如题目要求最小距离、最小次数。那么就有可能需要使用BFS来解决。题目中提到树，并且数据规模不大，可以通过递归来解决。再比如让你求出所有的组合，那么就很有可能使用回溯法，但是一定要注意数据规模，并进行必要的剪枝处理，不然容易超时。再比如，题目中包含以下信息：</p><p>●　比当前元素更大的下一个元素；</p><p>●　比当前元素更大的前一个元素；</p><p>●　比当前元素更小的下一个元素；</p><p>●　比当前元素更小的前一个元素。</p><p>这就告诉我们很可能可以使用单调栈来解决。比如第42题接雨水，第84题柱状图中最大的矩形，第496题下一个更大的元素I，第503题下一个更大的元素II，第739题每日温度，第901题股票的价格跨度等。</p><p>① 单调递减栈：找“大佬”</p><p>当一个新元素 $X$ 准备入栈，发现它比栈顶元素 $Top$ <strong>大</strong>时，$Top$ 就被“踢出”了。</p><ul><li><strong>对 $Top$ 来说</strong>：这个把扣踢出去的 $X$，就是它在右边遇到的<strong>第一个比它大</strong>的元素。</li><li><strong>应用场景</strong>：<ul><li><strong>下一个更大元素</strong>：如你刚才做的题目。</li><li><strong>接雨水</strong>：寻找左右两边的高地，形成一个“凹槽”来存水。</li><li><strong>每日温度</strong>：找多少天后气温会升高。</li></ul></li></ul><p>② 单调递增栈：找“洼地”</p><p>当一个新元素 $X$ 准备入栈，发现它比栈顶元素 $Top$ <strong>小</strong>时，$Top$ 同样被“踢出”。</p><ul><li><strong>对 $Top$ 来说</strong>：这个 $X$ 就是它右边遇到的<strong>第一个比它小</strong>的元素。</li><li><strong>应用场景</strong>：<ul><li><strong>柱状图中最大的矩形</strong>：为了让矩形尽可能宽，高度必须受限于两边第一个比它矮的柱子。</li><li><strong>去除重复字母</strong>：在保证字典序最小的前提下保留字符。</li></ul></li></ul><p>无论哪种栈，入栈和出栈的瞬间都隐藏了关键信息：</p><div class="table-container"><table><thead><tr><th><strong>目标</strong></th><th><strong>使用哪种栈</strong></th><th><strong>什么时候记录答案？</strong></th></tr></thead><tbody><tr><td><strong>右侧</strong>第一个比它<strong>大</strong></td><td>单调递减栈</td><td>元素 $X$ <strong>弹出</strong>栈顶 $Top$ 时，答案是 $X$</td></tr><tr><td><strong>左侧</strong>第一个比它<strong>大</strong></td><td>单调递减栈</td><td>元素 $X$ <strong>压入</strong>栈中后，答案是此时的新栈顶下方那个数</td></tr><tr><td><strong>右侧</strong>第一个比它<strong>小</strong></td><td>单调递增栈</td><td>元素 $X$ <strong>弹出</strong>栈顶 $Top$ 时，答案是 $X$</td></tr><tr><td><strong>左侧</strong>第一个比它<strong>小</strong></td><td>单调递增栈</td><td>元素 $X$ <strong>压入</strong>栈中后，答案是此时的新栈顶下方那个数</td></tr></tbody></table></div><h1 id="常考题"><a href="#常考题" class="headerlink" title="常考题"></a>常考题</h1><ol><li>数据结构之王：LRU 缓存 (Least Recently Used)</li></ol><p>这道题是<strong>大厂出镜率最高</strong>的题目，没有之一。它考察的不仅是算法，更是你对<strong>复合数据结构</strong>的理解。</p><ul><li><strong>题目内容</strong>：实现 <code>LRU缓存</code> 机制，要求 <code>get</code> 和 <code>put</code> 都是 $O(1)$ 时间复杂度。</li><li><strong>考察核心</strong>：<strong>哈希表 + 双向链表</strong>。</li><li><strong>面试官想看什么</strong>：你是否能手写双向链表的节点删除和插入，是否考虑了线程安全（如果是 Java 面试），以及对空间复杂度的权衡。</li></ul><ol><li>数组与指针：接雨水 (Trapping Rain Water)</li></ol><p>如果说 LRU 是数据结构的必考，那么接雨水就是<strong>双指针和单调栈</strong>的巅峰。</p><ul><li><strong>题目内容</strong>：给定 $n$ 个非负整数表示每个柱子的高度，计算按此排列的柱子，下雨能接多少雨水。</li><li><strong>考察核心</strong>：<strong>双指针</strong>、<strong>单调栈</strong>或<strong>动态规划</strong>。</li><li><strong>面试官想看什么</strong>：你能不能从 $O(n^2)$ 的暴力解法优化到 $O(n)$。这道题能一眼看出一个候选人的算法上限。</li></ul><ol><li>树论基础：二叉树的最近公共祖先 (LCA)</li></ol><p>二叉树题目中，LCA 是区分度最高的一道。</p><ul><li><strong>题目内容</strong>：给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。</li><li><strong>考察核心</strong>：<strong>递归</strong>、<strong>回溯</strong>。</li><li><strong>面试官想看什么</strong>：你对递归边界的控制能力。很多候选人能写出代码，但说不清为什么当 <code>left</code> 和 <code>right</code> 都不为空时，当前节点就是祖先。</li></ul><ol><li>排序与选择：前 K 个高频元素 / 第 K 大的数</li></ol><p>这类题目直接对应业务场景（如：排行榜、热门搜索）。</p><ul><li><strong>考察核心</strong>：<strong>堆排序 (Heap)</strong> 或 <strong>快速选择 (Quick Select)</strong>。</li><li><strong>面试官想看什么</strong>：如果你用 <code>sort</code> 函数，面试直接结束。他想看你能不能用 $O(n \log k)$ 甚至 $O(n)$ 的时间复杂度解决问题，并让你手写一个大顶堆或小顶堆。</li></ul><ol><li>动态规划入门与进阶：最长递增子序列 (LIS)</li></ol><p>大厂面试必有一道 DP（动态规划）。</p><ul><li><strong>考察核心</strong>：<strong>状态转移方程的推导</strong>。</li><li><strong>面试官想看什么</strong>：你能写出 $O(n^2)$ 的 DP 是及格，如果你能写出 $O(n \log n)$ 的“贪心 + 二分查找”解法，那就是优秀。</li></ul><p><strong>大厂面试高频分类表</strong></p><div class="table-container"><table><thead><tr><th><strong>类别</strong></th><th><strong>必刷经典题</strong></th><th><strong>核心技巧</strong></th></tr></thead><tbody><tr><td><strong>链表</strong></td><td>反转链表、环形链表 II</td><td>快慢指针、虚拟头节点</td></tr><tr><td><strong>二叉树</strong></td><td>层次遍历、锯齿形遍历</td><td>BFS、队列应用</td></tr><tr><td><strong>字符串</strong></td><td>无重复字符的最长子串</td><td>滑动窗口、哈希表</td></tr><tr><td><strong>排序</strong></td><td>手写快排、归并排序</td><td>分治法、递归</td></tr><tr><td><strong>场景题</strong></td><td>100亿个URL去重、大文件排序</td><td>布隆过滤器、外部排序</td></tr></tbody></table></div><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://noworneverev.github.io/leetcode_101">https://noworneverev.github.io/leetcode_101</a></li><li><a href="https://github.com/krahets/LeetCode-Book">krahets/LeetCode-Book: 《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码</a></li><li><a href="https://zhuanlan.zhihu.com/p/666207645"> 一个标星25.5k⭐开源的编程题解仓库:leetcode - 知乎</a></li><li><a href="https://github.com/doocs/leetcode">doocs/leetcode: 🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer（第 2 版）》、《程序员面试金典（第 6 版）》题解</a></li><li><a href="https://docs.qq.com/doc/DUGxZZnNjVm53S2NF?no_promotion=1">算法</a></li><li><a href="https://github.com/krahets/hello-algo">krahets/hello-algo</a></li><li><a href="https://github.com/halfrost/LeetCode-Go">https://github.com/halfrost/LeetCode-Go</a></li><li><a href="https://labuladong.online/zh/">labuladong.online</a></li><li><a href="https://www.programmercarl.com/">代码随想录</a></li><li><a href="https://codetop.cc/">CodeTop 面试题目总结</a></li><li><a href="https://github.com/EndlessCheng/codeforces-go/blob/master/leetcode/SOLUTIONS.md">codeforces-go/leetcode/SOLUTIONS.md at master · EndlessCheng/codeforces-go</a></li><li><a href="https://leetcode.cn/discuss/post/RvFUtj/">分享｜如何科学刷题？ - 讨论 - 力扣（LeetCode）</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/27/%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95%E9%A2%98%E8%A7%A3%E6%9E%90/</id>
    <link href="https://www.sekyoro.top/2026/01/27/%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95%E9%A2%98%E8%A7%A3%E6%9E%90/"/>
    <published>2026-01-27T02:34:25.000Z</published>
    <summary>
      <![CDATA[<p>再次总结经典算法题目。<br>]]>
    </summary>
    <title>经典算法题解析</title>
    <updated>2026-03-12T13:46:15.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="算法" scheme="https://www.sekyoro.top/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="线段树" scheme="https://www.sekyoro.top/tags/%E7%BA%BF%E6%AE%B5%E6%A0%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>线段树（Segment Tree）是处理区间问题的神器。如果你厌倦了 $O(N)$ 的暴力遍历，又觉得前缀和（Prefix Sum）处理不了动态修改，那么线段树就是那个能带你进入 $O(\log N)$ 世界的理想工具。<br><span id="more"></span></p><h1 id="线段树"><a href="#线段树" class="headerlink" title="线段树"></a>线段树</h1><blockquote><p>线段树就是把一段序列变成一棵二叉树，通过<strong>分治</strong>的思想，让原本需要扫一遍的操作在对数时间内完成。</p></blockquote><p>线段树 segmentTree 是一个二叉树，每个结点保存数组 nums 在区间 [s,e] 的最小值、最大值或者总和等信息。线段树可以用树也可以用数组（堆式存储）来实现。对于数组实现，假设根结点的下标为 0，如果一个结点在数组的下标为 node，那么它的左子结点下标为 node×2+1，右子结点下标为 node×2+2。</p><h2 id="线段树概念"><a href="#线段树概念" class="headerlink" title="线段树概念"></a>线段树概念</h2><p>线段树是一种<strong>平衡二叉树</strong>，它将一个长度为 $N$ 的数组划分为多个区间。每个节点代表一个区间 $[L, R]$：</p><ul><li><strong>根节点</strong>代表整个区间 $[1, N]$。</li><li><strong>叶子节点</strong>代表长度为 <strong>1</strong> 的元区间 $[i, i]$。</li><li><strong>内部节点</strong> $[L, R]$ 的左儿子代表 $[L, \text{mid}]$，右儿子代表 $[\text{mid}+1, R]$，其中 $\text{mid} = \lfloor (L+R)/2 \rfloor$。</li></ul><p><strong>核心优势：时空复杂度</strong></p><div class="table-container"><table><thead><tr><th><strong>操作</strong></th><th><strong>复杂度</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>Build (建树)</td><td>$O(N)$</td><td>预处理整个数组。</td></tr><tr><td>Query (区间查询)</td><td>$O(\log N)$</td><td>如查询 $[L, R]$ 的和、最大值等。</td></tr><tr><td>Update (修改)</td><td>$O(\log N)$</td><td>单点修改或区间修改。</td></tr><tr><td>空间复杂度</td><td>$O(4N)$</td><td>通常需要开 4 倍数组空间以防越界。</td></tr></tbody></table></div><p>线段树本质上是将一个区间 $[1, n]$ 递归地对半拆分，直到拆成长度为 1 的叶子节点。每个节点存储该区间的信息（如和、最大值等）。</p><ul><li><strong>根节点</strong>：代表区间 $[1, n]$。</li><li><strong>子节点</strong>：节点 $[L, R]$ 的左孩子代表 $[L, \text{mid}]$，右孩子代表 $[\text{mid}+1, R]$，其中 $\text{mid} = \lfloor (L+R)/2 \rfloor$。</li></ul><p>此外还涉及懒惰传播，</p><p><strong>核心逻辑</strong>：如果当前节点代表的区间被修改区间<strong>完全包含</strong>，就在该节点打上一个“懒标记”（Lazy Tag），记录下这次修改，而不继续递归更新它的所有子孙。</p><p><strong>按需下推（Push Down）</strong>：只有当下次需要访问或查询该节点的子节点时，才顺便将标记传下去。这避免了大量不必要的 $O(N)$ 更新。</p><p>线段树是将数组划分为一棵<strong>平衡二叉树</strong>。它是这三者中最强大的，几乎可以处理任何区间相关的问题。</p><ul><li><strong>核心思想</strong>：分治法。根节点管 $[1, n]$，左右孩子各管一半。</li><li><strong>操作原理</strong>：<ul><li><strong>懒标记 (Lazy Tag)</strong>：这是线段树处理区间修改的精髓。当修改整个区间时，先在节点打个“标记”并返回，等到真正访问子节点时再下传。</li></ul></li><li><strong>优点</strong>：极其通用。不仅能求和，还能处理区间最大值、区间平方和、区间覆盖、甚至区间矩阵乘法。</li><li><strong>缺点</strong>：代码量大，空间开销大（需开 $4n$ 的数组），运行常数比树状数组大。</li><li><strong>适用场景</strong>：复杂的区间操作（如：区间加 + 区间乘 + 区间查询最大值）。</li></ul><p>只要一个属性满足<strong>“区间加法”</strong>（即：你可以通过两个子区间的信息推导出父区间的信息），线段树就能维护它：</p><ul><li><strong>区间求和</strong>：<code>sum = left.sum + right.sum</code></li><li><strong>区间最值 (Max/Min)</strong>：<code>max = max(left.max, right.max)</code></li><li><strong>区间最大公约数 (GCD)</strong>：<code>gcd = gcd(left.gcd, right.gcd)</code></li><li><strong>区间连续最大子段和</strong>：多用于动态规划（GSS系列问题）。</li><li><strong>甚至区间的矩阵乘法</strong>。 </li></ul><p>线段树的修改操作分为两种：<strong>单点修改</strong>（Point Update）和<strong>区间修改</strong>（Range Update）。</p><p>由于线段树是一个递归结构，修改的核心逻辑都是：<strong>从根节点出发，递归地寻找目标区间，修改后再向上回溯更新父节点。</strong></p><hr><h3 id="1-单点修改-Point-Update"><a href="#1-单点修改-Point-Update" class="headerlink" title="1. 单点修改 (Point Update)"></a>1. 单点修改 (Point Update)</h3><p>这是最基础的操作。假设我们要修改数组中第 $i$ 个位置的值。</p><ul><li><strong>流程</strong>：<ol><li>从根节点开始。</li><li>如果当前节点是叶子节点且正是我们要找的 $i$，直接修改它。</li><li>如果不是，判断 $i$ 在左半边还是右半边，递归下去。</li><li><strong>关键步骤</strong>：在递归返回的过程中，执行 <code>push_up</code> 操作，用更新后的子节点值重新计算当前父节点的值。</li></ol></li><li><strong>复杂度</strong>：$O(\log n)$，因为只需要走一条从根到叶子的路径。</li></ul><p><strong>建树 build 函数</strong></p><p>我们在结点 node 保存数组 nums 在区间 [s,e] 的总和。</p><p>s=e 时，结点 node 是叶子结点，它保存的值等于 nums[s]。</p><p>s&lt;e 时，结点 node 的左子结点保存区间 [s,⌊ (s+e )/2⌋] 的总和，右子结点保存区间 [⌊ (s+e)/2 ⌋+1,e] 的总和，那么结点 node 保存的值等于它的两个子结点保存的值之和。</p><p>假设 nums 的大小为 n，我们规定根结点 node=0 保存区间 [0,n−1] 的总和，然后自下而上递归地建树。</p><p><strong>单点修改 change 函数</strong></p><p>当我们要修改 nums[index] 的值时，我们找到对应区间 [index,index] 的叶子结点，直接修改叶子结点的值为 val，并自下而上递归地更新父结点的值。</p><p><strong>范围求和 range 函数</strong></p><p>给定区间 [left,right] 时，我们将区间 [left,right] 拆成多个结点对应的区间。</p><p>如果结点 node 对应的区间与 [left,right] 相同，可以直接返回该结点的值，即当前区间和。</p><p>如果结点 node 对应的区间与 [left,right] 不同，设左子结点对应的区间的右端点为 m，那么将区间 [left,right] 沿点 m 拆成两个区间，分别计算左子结点和右子结点。</p><p>从根结点开始递归地拆分区间 [left,right]</p><hr><h3 id="2-区间修改-Range-Update"><a href="#2-区间修改-Range-Update" class="headerlink" title="2. 区间修改 (Range Update)"></a>2. 区间修改 (Range Update)</h3><p>如果你要给区间 $[L, R]$ 内的每个数都加 $v$，这就是线段树真正展现威力的时候。这里必须引入我们在第一个问题中提到的<strong>懒惰标记 (Lazy Tag)</strong>。</p><ul><li><p><strong>核心逻辑（三部曲）</strong>：</p><ol><li><strong>下推 (Push Down)</strong>：如果当前节点有旧的懒标记，先传给儿子，把账结清。</li><li><strong>修改 (Update)</strong>：<ul><li>如果当前节点表示的范围<strong>完全包含</strong>在目标区间 $[L, R]$ 内，直接更新当前节点值，打上懒标记，然后<strong>立即返回</strong>（不再往下递归）。</li><li>如果没有完全包含，递归左右子树。</li></ul></li><li><strong>上推 (Push Up)</strong>：递归回来后，更新当前节点，确保父节点的数据是准确的</li></ol><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> val;  <span class="comment">// 存储区间和</span></span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> lazy; <span class="comment">// 懒标记</span></span><br><span class="line">&#125; tree[MAXN * <span class="number">4</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新父节点信息</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">push_up</span><span class="params">(<span class="type">int</span> node)</span> </span>&#123;</span><br><span class="line">    tree[node].val = tree[node*<span class="number">2</span>].val + tree[node*<span class="number">2</span><span class="number">+1</span>].val;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下传懒标记</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">push_down</span><span class="params">(<span class="type">int</span> node, <span class="type">int</span> start, <span class="type">int</span> end)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (tree[node].lazy != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> mid = (start + end) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> lazy_val = tree[node].lazy;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 更新左儿子</span></span><br><span class="line">        tree[node*<span class="number">2</span>].lazy += lazy_val;</span><br><span class="line">        tree[node*<span class="number">2</span>].val += lazy_val * (mid - start + <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 更新右儿子</span></span><br><span class="line">        tree[node*<span class="number">2</span><span class="number">+1</span>].lazy += lazy_val;</span><br><span class="line">        tree[node*<span class="number">2</span><span class="number">+1</span>].val += lazy_val * (end - mid);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 清除当前节点的标记</span></span><br><span class="line">        tree[node].lazy = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 区间修改：将 [L, R] 范围内的数增加 v</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> node, <span class="type">int</span> start, <span class="type">int</span> end, <span class="type">int</span> L, <span class="type">int</span> R, <span class="type">int</span> v)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 1. 完全包含：打标记，改值，返回</span></span><br><span class="line">    <span class="keyword">if</span> (L &lt;= start &amp;&amp; end &lt;= R) &#123;</span><br><span class="line">        tree[node].val += (<span class="type">long</span> <span class="type">long</span>)v * (end - start + <span class="number">1</span>);</span><br><span class="line">        tree[node].lazy += v;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 下传旧标记</span></span><br><span class="line">    <span class="built_in">push_down</span>(node, start, end);</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> mid = (start + end) / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span> (L &lt;= mid) <span class="built_in">update</span>(node*<span class="number">2</span>, start, mid, L, R, v);</span><br><span class="line">    <span class="keyword">if</span> (R &gt; mid)  <span class="built_in">update</span>(node*<span class="number">2</span><span class="number">+1</span>, mid<span class="number">+1</span>, end, L, R, v);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 回溯更新父节点</span></span><br><span class="line">    <span class="built_in">push_up</span>(node);</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SegmentTree</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">int</span> n;</span><br><span class="line">    vector&lt;<span class="type">long</span> <span class="type">long</span>&gt; tree; <span class="comment">// 存储区间和</span></span><br><span class="line">    vector&lt;<span class="type">long</span> <span class="type">long</span>&gt; lazy; <span class="comment">// 存储懒标记</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向上更新</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">pushUp</span><span class="params">(<span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        tree[k] = tree[k &lt;&lt; <span class="number">1</span>] + tree[k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向下推标记</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">pushDown</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (lazy[k] != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">int</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">            <span class="comment">// 更新左孩子</span></span><br><span class="line">            lazy[k &lt;&lt; <span class="number">1</span>] += lazy[k];</span><br><span class="line">            tree[k &lt;&lt; <span class="number">1</span>] += lazy[k] * (mid - l + <span class="number">1</span>);</span><br><span class="line">            <span class="comment">// 更新右孩子</span></span><br><span class="line">            lazy[k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>] += lazy[k];</span><br><span class="line">            tree[k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>] += lazy[k] * (r - mid);</span><br><span class="line">            <span class="comment">// 清空当前标记</span></span><br><span class="line">            lazy[k] = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 建树</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">build</span><span class="params">(<span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> k, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (l == r) &#123;</span><br><span class="line">            tree[k] = nums[l - <span class="number">1</span>]; <span class="comment">// 注意 nums 是 0 索引</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">        <span class="built_in">build</span>(nums, k &lt;&lt; <span class="number">1</span>, l, mid);</span><br><span class="line">        <span class="built_in">build</span>(nums, k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>, mid + <span class="number">1</span>, r);</span><br><span class="line">        <span class="built_in">pushUp</span>(k);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span> l, <span class="type">int</span> r, <span class="type">int</span> qL, <span class="type">int</span> qR, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= l &amp;&amp; r &lt;= qR) &#123;</span><br><span class="line">            lazy[k] += val;</span><br><span class="line">            tree[k] += (<span class="type">long</span> <span class="type">long</span>)val * (r - l + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">pushDown</span>(k, l, r);</span><br><span class="line">        <span class="type">int</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= mid) <span class="built_in">update</span>(k &lt;&lt; <span class="number">1</span>, l, mid, qL, qR, val);</span><br><span class="line">        <span class="keyword">if</span> (qR &gt; mid) <span class="built_in">update</span>(k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>, mid + <span class="number">1</span>, r, qL, qR, val);</span><br><span class="line">        <span class="built_in">pushUp</span>(k);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">query</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span> l, <span class="type">int</span> r, <span class="type">int</span> qL, <span class="type">int</span> qR)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= l &amp;&amp; r &lt;= qR) <span class="keyword">return</span> tree[k];</span><br><span class="line">        <span class="built_in">pushDown</span>(k, l, r);</span><br><span class="line">        <span class="type">int</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> res = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= mid) res += <span class="built_in">query</span>(k &lt;&lt; <span class="number">1</span>, l, mid, qL, qR);</span><br><span class="line">        <span class="keyword">if</span> (qR &gt; mid) res += <span class="built_in">query</span>(k &lt;&lt; <span class="number">1</span> | <span class="number">1</span>, mid + <span class="number">1</span>, r, qL, qR);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">SegmentTree</span>(<span class="type">const</span> vector&lt;<span class="type">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        n = nums.<span class="built_in">size</span>();</span><br><span class="line">        tree.<span class="built_in">assign</span>(<span class="number">4</span> * n, <span class="number">0</span>);</span><br><span class="line">        lazy.<span class="built_in">assign</span>(<span class="number">4</span> * n, <span class="number">0</span>);</span><br><span class="line">        <span class="built_in">build</span>(nums, <span class="number">1</span>, <span class="number">1</span>, n);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> qL, <span class="type">int</span> qR, <span class="type">int</span> val)</span> </span>&#123; <span class="built_in">update</span>(<span class="number">1</span>, <span class="number">1</span>, n, qL, qR, val); &#125;</span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">query</span><span class="params">(<span class="type">int</span> qL, <span class="type">int</span> qR)</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">query</span>(<span class="number">1</span>, <span class="number">1</span>, n, qL, qR); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>对于一个长度为 $n$ 的区间，线段树是一棵二分树：</p><ol><li><p><strong>理想情况</strong>：如果 $n$ 是 2 的幂，总节点数约为 $2n-1$。</p></li><li><p><strong>最坏情况</strong>：如果 $n = 2^k + 1$，为了保证最后一层能放下所有的叶子节点，树的高度会增加一层。此时索引地址会跨越到接近 $4n$ 的位置。</p><p>因此，为了防止数组越界，<strong>开 4 倍空间是最稳妥的准则。</strong></p></li></ol><p><strong><code>pushDown</code> 是为了“去”</strong>：你要访问孩子了，所以得把欠孩子的更新给它们。</p><p><strong><code>pushUp</code> 是为了“回”</strong>：孩子更新完了，你作为父亲得汇总最新的状态。</p><p><strong>区间判断逻辑</strong>：</p><ul><li><code>qL &lt;= mid</code>：说明左孩子管辖的范围里有你需要的部分。</li><li><code>qR &gt; mid</code>：说明右孩子管辖的范围里有你需要的部分</li></ul></li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 动态开点线段树（区间加法，区间求和）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> val;  <span class="comment">// 区间和</span></span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> add;  <span class="comment">// Lazy Tag (懒惰标记)</span></span><br><span class="line">    <span class="type">int</span> left, right; <span class="comment">// 左右孩子在 nodes 数组中的索引</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">Node</span>() : <span class="built_in">val</span>(<span class="number">0</span>), <span class="built_in">add</span>(<span class="number">0</span>), <span class="built_in">left</span>(<span class="number">0</span>), <span class="built_in">right</span>(<span class="number">0</span>) &#123;&#125;</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_">DynamicSegmentTree</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    vector&lt;Node&gt; nodes;</span><br><span class="line">    <span class="type">int</span> root;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> L, R; <span class="comment">// 树的整体范围，如 [0, 1e9]</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建新节点的辅助函数</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">newNode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        nodes.<span class="built_in">emplace_back</span>();</span><br><span class="line">        <span class="keyword">return</span> nodes.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向上更新：由孩子节点更新当前节点</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">pushUp</span><span class="params">(<span class="type">int</span> node)</span> </span>&#123;</span><br><span class="line">        nodes[node].val = nodes[nodes[node].left].val + nodes[nodes[node].right].val;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向下推标记：将当前节点的标记传给孩子</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">pushDown</span><span class="params">(<span class="type">int</span> node, <span class="type">long</span> <span class="type">long</span> leftRange, <span class="type">long</span> <span class="type">long</span> rightRange)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!nodes[node].left) nodes[node].left = <span class="built_in">newNode</span>();</span><br><span class="line">        <span class="keyword">if</span> (!nodes[node].right) nodes[node].right = <span class="built_in">newNode</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (nodes[node].add == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> mid = leftRange + (rightRange - leftRange) / <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 左孩子更新</span></span><br><span class="line">        nodes[nodes[node].left].add += nodes[node].add;</span><br><span class="line">        nodes[nodes[node].left].val += nodes[node].add * (mid - leftRange + <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 右孩子更新</span></span><br><span class="line">        nodes[nodes[node].right].add += nodes[node].add;</span><br><span class="line">        nodes[nodes[node].right].val += nodes[node].add * (rightRange - mid);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 清空当前标记</span></span><br><span class="line">        nodes[node].add = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 内部递归更新函数</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span>&amp; node, <span class="type">long</span> <span class="type">long</span> l, <span class="type">long</span> <span class="type">long</span> r, <span class="type">long</span> <span class="type">long</span> qL, <span class="type">long</span> <span class="type">long</span> qR, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) node = <span class="built_in">newNode</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果当前区间被完全包含</span></span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= l &amp;&amp; r &lt;= qR) &#123;</span><br><span class="line">            nodes[node].add += val;</span><br><span class="line">            nodes[node].val += (<span class="type">long</span> <span class="type">long</span>)val * (r - l + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">pushDown</span>(node, l, r);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= mid) <span class="built_in">update</span>(nodes[node].left, l, mid, qL, qR, val);</span><br><span class="line">        <span class="keyword">if</span> (qR &gt; mid) <span class="built_in">update</span>(nodes[node].right, mid + <span class="number">1</span>, r, qL, qR, val);</span><br><span class="line">        <span class="built_in">pushUp</span>(node);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 内部递归查询函数</span></span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">query</span><span class="params">(<span class="type">int</span> node, <span class="type">long</span> <span class="type">long</span> l, <span class="type">long</span> <span class="type">long</span> r, <span class="type">long</span> <span class="type">long</span> qL, <span class="type">long</span> <span class="type">long</span> qR)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!node) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= l &amp;&amp; r &lt;= qR) <span class="keyword">return</span> nodes[node].val;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">pushDown</span>(node, l, r);</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">long</span> <span class="type">long</span> res = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (qL &lt;= mid) res += <span class="built_in">query</span>(nodes[node].left, l, mid, qL, qR);</span><br><span class="line">        <span class="keyword">if</span> (qR &gt; mid) res += <span class="built_in">query</span>(nodes[node].right, mid + <span class="number">1</span>, r, qL, qR);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">DynamicSegmentTree</span>(<span class="type">long</span> <span class="type">long</span> start, <span class="type">long</span> <span class="type">long</span> end) : <span class="built_in">L</span>(start), <span class="built_in">R</span>(end) &#123;</span><br><span class="line">        nodes.<span class="built_in">emplace_back</span>(); <span class="comment">// 占位，让索引从1开始</span></span><br><span class="line">        root = <span class="built_in">newNode</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">long</span> <span class="type">long</span> qL, <span class="type">long</span> <span class="type">long</span> qR, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">update</span>(root, L, R, qL, qR, val);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">long</span> <span class="type">long</span> <span class="title">query</span><span class="params">(<span class="type">long</span> <span class="type">long</span> qL, <span class="type">long</span> <span class="type">long</span> qR)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">query</span>(root, L, R, qL, qR);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>传统的线段树需要开辟 $4N$ 大小的数组。如果区间范围是 $[0, 10^9]$，数组根本开不下。 <strong>动态开点</strong>的思路是：<strong>按需取用</strong>。只有当某个区间被访问或修改时，我们才真正创建这个节点。这样，空间复杂度就从 $O(N)$ 降到了 $O(Q \log N)$（$Q$ 为操作次数），完美解决大范围坐标问题。</p><p><strong>[715. Range 模块]</strong>：涉及到区间的添加、查询、删除，是动态开点线段树的绝佳舞台。</p><p><strong>[732. 我的日程安排表 III]</strong>：求区间最大重叠数，也就是线段树维护区间最大值。</p><p>A. 树状数组 (BIT) —— 离散化与计数</p><p>树状数组最经典的用法不是简单的求和，而是配合<strong>离散化</strong>来解决<strong>逆序对</strong>或<strong>元素计数</strong>问题。</p><ul><li><strong><a href="https://leetcode.cn/problems/range-sum-query-mutable/">307. 区域和检索 - 数组可修改</a> (Medium)</strong><ul><li><strong>地位</strong>：BIT 的“Hello World”。单点修改，区间求和。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/count-of-smaller-numbers-after-self/">315. 计算右侧小于当前元素的个数</a> (Hard)</strong><ul><li><strong>核心</strong>：从右往左遍历数组，利用 BIT 动态维护“已经出现了哪些数”。这是 BIT 在面试中最常出现的形态。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/reverse-pairs/">493. 翻转对</a> (Hard)</strong><ul><li><strong>核心</strong>：在处理 $nums[i] &gt; 2 \times nums[j]$ 时，BIT 配合离散化可以极其优雅地完成计数。</li></ul></li></ul><p>B. 线段树 (Segment Tree) —— 区间更新的艺术</p><p>线段树的真正威力在于处理 <strong>区间修改</strong>（比如：把 $[l, r]$ 全都加 $v$，或者全部变成 $v$）。</p><ul><li><strong><a href="https://leetcode.cn/problems/range-module/">715. Range 模块</a> (Hard)</strong><ul><li><strong>地位</strong>：管理实数区间。需要线段树支持区间的添加、查询和删除。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/falling-squares/">699. 掉落的方块</a> (Hard)</strong><ul><li><strong>核心</strong>：区间高度更新 + 区间最大值查询。这是典型的“动态维护最大值”问题。</li></ul></li><li><strong><a href="https://leetcode.cn/problems/my-calendar-iii/">732. 我的日程安排表 III</a> (Hard)</strong><ul><li><strong>核心</strong>：求区间的最大重叠次数（$k$-次覆盖）。可以使用动态开点线段树。</li></ul></li></ul><h2 id="经典例题"><a href="#经典例题" class="headerlink" title="经典例题"></a>经典例题</h2><p>线段树的应用极广，通常只要某种信息满足<strong>结合律</strong>（即可以通过左右孩子的信息合并得到父节点信息），就能用线段树维护。</p><p>「线段树求解最长公共上升子序列问题」的 pushUp 操作。</p><p>线段树区间合并法解决多次询问的「区间最长连续上升序列问题」和「区间最大子段和问题」</p><p><a href="https://leetcode.cn/problems/range-sum-query-mutable/">307. 区域和检索 - 数组可修改</a></p><p>给你一个数组 <code>nums</code> ，请你完成两类查询。</p><ol><li>其中一类查询要求 <strong>更新</strong> 数组 <code>nums</code> 下标对应的值</li><li>另一类查询要求返回数组 <code>nums</code> 中索引 <code>left</code> 和索引 <code>right</code> 之间（ <strong>包含</strong> ）的nums元素的 <strong>和</strong> ，其中 <code>left &lt;= right</code></li></ol><p>实现 <code>NumArray</code> 类：</p><ul><li><code>NumArray(int[] nums)</code> 用整数数组 <code>nums</code> 初始化对象</li><li><code>void update(int index, int val)</code> 将 <code>nums[index]</code> 的值 <strong>更新</strong> 为 <code>val</code></li><li><code>int sumRange(int left, int right)</code> 返回数组 <code>nums</code> 中索引 <code>left</code> 和索引 <code>right</code> 之间（ <strong>包含</strong> ）的nums元素的 <strong>和</strong> （即，<code>nums[left] + nums[left + 1], ..., nums[right]</code>）</li></ul><p>最大子数组之和也可以使用线段树分治解法</p><h1 id="树状数组"><a href="#树状数组" class="headerlink" title="树状数组"></a>树状数组</h1><p>树状数组是一种可以<strong>动态维护序列前缀和的数据结构</strong>（序列下标从 1 开始），它的功能是：</p><p>单点修改 add(index,val)：把序列第 index 个数增加 val；</p><p>区间查询 prefixSum(index)：查询前 index 个元素的前缀和。</p><p>树状数组是一种极其精巧的结构，利用<strong>二进制分解</strong>来维护区间信息。它在“单点修改”和“前缀和查询”之间达到了完美的平衡。</p><ul><li><strong>核心思想</strong>：利用 <code>lowbit(x) = x &amp; -x</code> 将数组划分为多个 $2^k$ 长度的块。</li><li><strong>操作原理</strong>：<ul><li><strong>单点修改</strong>：向上进位（<code>x += lowbit(x)</code>），更新所有包含该点的管辖区间。</li><li><strong>前缀查询</strong>：向左下跳（<code>x -= lowbit(x)</code>），拼凑出 $[1, x]$ 的总和。</li></ul></li><li><strong>优点</strong>：代码极短（核心仅几行），空间开销最小（$O(n)$），常数极小（运行速度极快）。</li><li><strong>缺点</strong>：功能相对局限，主要用于求和、最值等满足结合律且容易求逆的操作，难以原生支持复杂的区间修改（如区间乘法）。</li><li><strong>适用场景</strong>：动态维护前缀和、逆序对计算、单点修改+区间查询</li></ul><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p><strong>树状数组</strong>（Binary Indexed Tree, BIT），又称 <strong>Fenwick Tree</strong>，是一种极其精巧的数据结构。它主要用于高效地处理<strong>区间前缀和的查询</strong>与<strong>单点值的修改</strong>。</p><p>树状数组的本质是利用<strong>数字的二进制分解</strong>来决定每个节点管理的区间范围。</p><ul><li><strong>线段树</strong>是将区间平分成两半。</li><li><strong>树状数组</strong>是将区间 $[1, n]$ 拆分为若干个长度为 $2^k$ 的子区间。</li></ul><p>例如，查询前缀和 $S[13]$：</p><p>$13$ 的二进制是 $(1101)_2$。它可以拆解为：</p><p>$13 = 2^3 + 2^2 + 2^0 = 8 + 4 + 1$</p><p>对应的区间就是：$(0, 8] + (8, 12] + (12, 13]$。树状数组的每个位置正好存储了这些特定长度区间的和。</p><h3 id="灵魂函数：lowbit"><a href="#灵魂函数：lowbit" class="headerlink" title="灵魂函数：lowbit"></a>灵魂函数：<code>lowbit</code></h3><p>树状数组最神奇的地方在于它如何寻找“父亲”和“儿子”。这全靠 <code>lowbit</code> 操作：</p><script type="math/tex; mode=display">\text{lowbit}(x) = x \& (-x)</script><p>这个操作会返回 $x$ 二进制表示中<strong>最低位的 1 及其后面的 0</strong>。例如：</p><ul><li>$\text{lowbit}(6)$：$6$ 是 $(110)_2$，返回 $(010)_2 = 2$。</li><li>$\text{lowbit}(8)$：$8$ 是 $(1000)_2$，返回 $(1000)_2 = 8$。</li></ul><p><strong><code>lowbit</code> 的作用：</strong></p><ul><li><strong>节点 $x$ 管理的范围长度</strong>就是 $\text{lowbit}(x)$。</li><li><strong>查询前缀和</strong>：$x = x - \text{lowbit}(x)$（向左上方跳）。</li><li><strong>修改单点值</strong>：$x = x + \text{lowbit}(x)$（向右上方跳，更新受影响的父节点）</li></ul><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><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><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FenwickTree</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::vector&lt;<span class="type">int</span>&gt; tree;</span><br><span class="line">    <span class="type">int</span> n;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 核心：找到最低位的 1</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lowbit</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> x &amp; -x;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">FenwickTree</span>(<span class="type">int</span> size) : <span class="built_in">n</span>(size), <span class="built_in">tree</span>(size + <span class="number">1</span>, <span class="number">0</span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 单点更新：将 index 位置的值加上 delta</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> i, <span class="type">int</span> delta)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (; i &lt;= n; i += <span class="built_in">lowbit</span>(i)) &#123;</span><br><span class="line">            tree[i] += delta;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 查询前缀和：查询 [1, i] 的总和</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">query</span><span class="params">(<span class="type">int</span> i)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (; i &gt; <span class="number">0</span>; i -= <span class="built_in">lowbit</span>(i)) &#123;</span><br><span class="line">            sum += tree[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sum;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 区间查询：查询 [L, R] 的总和</span></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">queryRange</span><span class="params">(<span class="type">int</span> L, <span class="type">int</span> R)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (L &gt; R) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">query</span>(R) - <span class="built_in">query</span>(L - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在树状数组中，节点 <code>tree[i]</code> 负责的区间长度是由 $i$ 的二进制表示中<strong>最低位的 1 及其后面的 0</strong> 决定的。</p><p>对于索引 $i$，它负责的范围是：</p><script type="math/tex; mode=display">(i - lowbit(i), i]</script><p>或者写成闭区间形式：</p><script type="math/tex; mode=display">[i - lowbit(i) + 1, i]</script><p>其中，$lowbit(i) = i \&amp; (-i)$。</p><h3 id="树状数组-vs-线段树"><a href="#树状数组-vs-线段树" class="headerlink" title="树状数组 vs 线段树"></a>树状数组 vs 线段树</h3><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>树状数组 (BIT)</strong></th><th><strong>线段树 (Segment Tree)</strong></th></tr></thead><tbody><tr><td><strong>代码量</strong></td><td>极短（核心仅几行）</td><td>较长</td></tr><tr><td><strong>空间复杂度</strong></td><td>$O(n)$（严格 1 倍空间）</td><td>$O(4n)$</td></tr><tr><td><strong>查询/修改时间</strong></td><td>$O(\log n)$</td><td>$O(\log n)$</td></tr><tr><td><strong>常数</strong></td><td>非常小（极快）</td><td>较大</td></tr><tr><td><strong>功能扩展</strong></td><td>较难（多用于求和、最值）</td><td>极强（几乎所有区间问题）</td></tr><tr><td><strong>懒标记</strong></td><td>很难实现（一般不用）</td><td>原生支持</td></tr></tbody></table></div><div class="table-container"><table><thead><tr><th><strong>方法</strong></th><th><strong>区间修改 (Update)</strong></th><th><strong>区间查询 (Query)</strong></th><th><strong>缺点</strong></th></tr></thead><tbody><tr><td><strong>普通数组</strong></td><td>$O(n)$</td><td>$O(n)$</td><td>慢到无法忍受</td></tr><tr><td><strong>前缀和</strong></td><td>$O(n)$</td><td>$O(1)$</td><td>修改一次要重算整个前缀和数组</td></tr><tr><td><strong>线段树</strong></td><td>$O(\log n)$</td><td>$O(\log n)$</td><td><strong>唯一能平衡两者的平衡方案</strong></td></tr></tbody></table></div><h1 id="差分数组"><a href="#差分数组" class="headerlink" title="差分数组"></a>差分数组</h1><p>差分数组是前缀和的<strong>逆运算</strong>。它最核心的用途是在 $O(1)$ 时间内完成<strong>区间修改</strong>。适用于<strong>所有的修改都完成后，才进行最终查询</strong>的情况</p><ul><li><strong>核心思想</strong>：维护相邻元素的差值 $D[i] = A[i] - A[i-1]$。</li><li><strong>操作原理</strong>：<ul><li><strong>区间修改</strong>：给区间 $[L, R]$ 加上 $v$，只需令 $D[L] += v$ 和 $D[R+1] -= v$。</li><li><strong>还原原数组</strong>：对 $D$ 求一遍前缀和，即可得到修改后的原数组。</li></ul></li><li><strong>优点</strong>：极简，修改极快（$O(1)$）。</li><li><strong>缺点</strong>：不支持边改边查。每次查询某个值都需要 $O(n)$ 重新求前缀和。</li><li><strong>适用场景</strong>：有大量的区间修改操作，且所有修改完成后才进行查询。</li></ul><p><strong>前缀和数组</strong>是用来快速计算“区间和”的，那么<strong>差分数组</strong>就是它的“孪生兄弟”，专门用来快速处理<strong>“区间修改”</strong>。</p><p>差分数组是前缀和的<strong>逆运算</strong>。它最核心的用途是在 $O(1)$ 时间内完成<strong>区间修改</strong>。</p><ul><li><strong>核心思想</strong>：维护相邻元素的差值 $D[i] = A[i] - A[i-1]$。</li><li><strong>操作原理</strong>：<ul><li><strong>区间修改</strong>：给区间 $[L, R]$ 加上 $v$，只需令 $D[L] += v$ 和 $D[R+1] -= v$。</li><li><strong>还原原数组</strong>：对 $D$ 求一遍前缀和，即可得到修改后的原数组。</li></ul></li><li><strong>优点</strong>：极简，修改极快（$O(1)$）。</li><li><strong>缺点</strong>：不支持边改边查。每次查询某个值都需要 $O(n)$ 重新求前缀和。</li><li><strong>适用场景</strong>：有大量的区间修改操作，且所有修改完成后才进行查询。</li></ul><p>简单来说，差分数组能把 $O(n)$ 的区间增加操作，变成 $O(1)$ 的单点操作。</p><ol><li>核心定义</li></ol><p>假设有一个原数组 $A$：$A = [a_1, a_2, a_3, \dots, a_n]$。</p><p>我们构造一个差分数组 $D$，满足：</p><ul><li>$D[1] = A[1]$</li><li>$D[i] = A[i] - A[i-1]$ （对于 $i &gt; 1$）</li></ul><p><strong>它的神奇特性是：</strong></p><p>原数组 $A$ 的第 $i$ 项，等于差分数组 $D$ 前 $i$ 项的<strong>前缀和</strong>。</p><script type="math/tex; mode=display">A[i] = \sum_{j=1}^i D[j]</script><ol><li>为什么要用它？</li></ol><p>想象一个场景：你需要给数组 $A$ 的区间 $[L, R]$ 里的每一个数都加上 $v$。</p><ul><li><strong>常规做法</strong>：写个 <code>for</code> 循环从 $L$ 扫到 $R$，复杂度 $O(n)$。</li><li><strong>差分做法</strong>：只需修改 $D$ 数组的两个点：<ol><li><strong>$D[L] += v$</strong>：这会让从 $L$ 开始往后的所有 $A[i]$ 在求前缀和时都加上了 $v$。</li><li><strong>$D[R+1] -= v$</strong>：这会让从 $R+1$ 开始往后的所有 $A[i]$ 把刚才多加的 $v$ 给减回去。</li></ol></li></ul><p><strong>结论</strong>：一次区间修改只需两次赋值，复杂度 <strong>$O(1)$</strong>。</p><p>假设数组 $A = [3, 5, 2, 4, 1]$。</p><p>第一步：构建差分数组 $D$</p><ul><li><p>$D[1] = 3$</p></li><li><p>$D[2] = 5 - 3 = 2$</p></li><li><p>$D[3] = 2 - 5 = -3$</p></li><li><p>$D[4] = 4 - 2 = 2$</p></li><li><p>$D[5] = 1 - 4 = -3$</p><p>此时 $D = [3, 2, -3, 2, -3]$。</p></li></ul><p>第二步：执行区间修改</p><p>我们要给区间 $[2, 4]$（也就是元素 $5, 2, 4$）每个数都<strong>加上 10</strong>。</p><ul><li><p>$D[2] = 2 + 10 = 12$</p></li><li><p>$D[4+1] = D[5] = -3 - 10 = -13$</p><p>修改后的 $D = [3, 12, -3, 2, -13]$。</p></li></ul><p>第三步：还原原数组 $A$</p><p>对 $D$ 求前缀和：</p><ul><li>$A’[1] = 3$</li><li>$A’[2] = 3 + 12 = 15$ （原值 5 + 10，正确！）</li><li>$A’[3] = 15 + (-3) = 12$ （原值 2 + 10，正确！）</li><li>$A’[4] = 12 + 2 = 14$ （原值 4 + 10，正确！）</li><li>$A’[5] = 14 + (-13) = 1$ </li></ul><h3 id="经典题目"><a href="#经典题目" class="headerlink" title="经典题目"></a>经典题目</h3><p>给你一个二维整数数组 <code>logs</code> ，其中每个 <code>logs[i] = [birthi, deathi]</code> 表示第 <code>i</code> 个人的出生和死亡年份。</p><p>年份 <code>x</code> 的 <strong>人口</strong> 定义为这一年期间活着的人的数目。第 <code>i</code> 个人被计入年份 <code>x</code> 的人口需要满足：<code>x</code> 在闭区间 <code>[birthi, deathi - 1]</code> 内。注意，人不应当计入他们死亡当年的人口中。</p><p>返回 <strong>人口最多</strong> 且 <strong>最早</strong> 的年份。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">maximumPopulation</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; logs)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">delta</span><span class="params">(<span class="number">101</span>)</span></span>;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> offset = <span class="number">1950</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; p : logs) &#123;</span><br><span class="line">            delta[p[<span class="number">0</span>] - offset]++;</span><br><span class="line">            delta[p[<span class="number">1</span>] - offset]--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> max&#123;&#125;, res&#123;&#125;, cur&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; delta.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            cur += delta[i];</span><br><span class="line">            <span class="keyword">if</span> (cur &gt; max) &#123;</span><br><span class="line">                max = cur;</span><br><span class="line">                res = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res + offset;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>字母移位</strong></p><p>给你一个小写英文字母组成的字符串 <code>s</code> 和一个二维整数数组 <code>shifts</code> ，其中 <code>shifts[i] = [starti, endi, directioni]</code> 。对于每个 <code>i</code> ，将 <code>s</code> 中从下标 <code>starti</code> 到下标 <code>endi</code> （两者都包含）所有字符都进行移位运算，如果 <code>directioni = 1</code> 将字符向后移位，如果 <code>directioni = 0</code> 将字符向前移位。</p><p>将一个字符 <strong>向后</strong> 移位的意思是将这个字符用字母表中 <strong>下一个</strong> 字母替换（字母表视为环绕的，所以 <code>&#39;z&#39;</code> 变成 <code>&#39;a&#39;</code>）。类似的，将一个字符 <strong>向前</strong> 移位的意思是将这个字符用字母表中 <strong>前一个</strong> 字母替换（字母表是环绕的，所以 <code>&#39;a&#39;</code> 变成 <code>&#39;z&#39;</code> ）。</p><p>请你返回对 <code>s</code> 进行所有移位操作以后得到的最终字符串。</p><p>如果需要<strong>永远返回正数</strong>的取模结果（例如在处理循环数组下标时），可以使用这个通用的“数学模”技巧：</p><script type="math/tex; mode=display">\text{positive\_mod} = (a \% b + b) \% b</script><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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">string <span class="title">shiftingLetters</span><span class="params">(string s, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; shifts)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = s.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">delta</span><span class="params">(sz + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; shift : shifts) &#123;</span><br><span class="line">            <span class="keyword">if</span> (shift[<span class="number">2</span>] == <span class="number">1</span>) &#123;</span><br><span class="line">                delta[shift[<span class="number">0</span>]] += shift[<span class="number">2</span>];</span><br><span class="line">                delta[shift[<span class="number">1</span>] + <span class="number">1</span>] -= shift[<span class="number">2</span>];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                delta[shift[<span class="number">0</span>]] -= <span class="number">1</span>;</span><br><span class="line">                delta[shift[<span class="number">1</span>] + <span class="number">1</span>] += <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">nums</span><span class="params">(sz)</span></span>;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            acc += delta[i];</span><br><span class="line">            nums[i] = acc;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            <span class="type">int</span> n = (nums[i] % <span class="number">26</span> + <span class="number">26</span>) % <span class="number">26</span>;</span><br><span class="line">            s[i] = <span class="string">&#x27;a&#x27;</span> + (s[i] - <span class="string">&#x27;a&#x27;</span> + n) % <span class="number">26</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> s;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>零数组变换I</strong></p><p>给定一个长度为 <code>n</code> 的整数数组 <code>nums</code> 和一个二维数组 <code>queries</code>，其中 <code>queries[i] = [li, ri]</code>。</p><p>对于每个查询 <code>queries[i]</code>：</p><ul><li>在 <code>nums</code> 的下标范围 <code>[li, ri]</code> 内选择一个下标 子集。</li><li>将选中的每个下标对应的元素值减 1。</li></ul><p><strong>零数组</strong> 是指所有元素都等于 0 的数组。</p><p>如果在按顺序处理所有查询后，可以将 <code>nums</code> 转换为 <strong>零数组</strong> ，则返回 <code>true</code>，否则返回 <code>false</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isZeroArray</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; queries)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">delta</span><span class="params">(sz + <span class="number">1</span>)</span></span>;</span><br><span class="line">        delta[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            delta[i] = nums[i] - nums[i - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; q : queries) &#123;</span><br><span class="line">            delta[q[<span class="number">0</span>]] -= <span class="number">1</span>;</span><br><span class="line">            delta[q[<span class="number">1</span>] + <span class="number">1</span>] += <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            acc += delta[i];</span><br><span class="line">            <span class="keyword">if</span> (acc &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a href="https://leetcode.cn/problems/zero-array-transformation-ii/">零数组变换 II</a></p><p>给你一个长度为 <code>n</code> 的整数数组 <code>nums</code> 和一个二维数组 <code>queries</code>，其中 <code>queries[i] = [li, ri, vali]</code>。</p><p>每个 <code>queries[i]</code> 表示在 <code>nums</code> 上执行以下操作：</p><ul><li>将 <code>nums</code> 中 <code>[li, ri]</code> 范围内的每个下标对应元素的值 <strong>最多</strong> 减少 <code>vali</code>。</li><li>每个下标的减少的数值可以<strong>独立</strong>选择。</li></ul><p><strong>零数组</strong> 是指所有元素都等于 0 的数组。</p><p>返回 <code>k</code> 可以取到的 <strong>最小**</strong>非负<strong> 值，使得在 </strong>顺序<strong> 处理前 <code>k</code> 个查询后，<code>nums</code> 变成 </strong>零数组**。如果不存在这样的 <code>k</code>，则返回 -1。</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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isValid</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; queries, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">delta</span><span class="params">(sz + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="comment">// 初始化</span></span><br><span class="line">        delta[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            delta[i] = nums[i] - nums[i - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; k; i++) &#123;</span><br><span class="line">            <span class="keyword">auto</span>&amp; q = queries[i];</span><br><span class="line">            delta[q[<span class="number">0</span>]] -= q[<span class="number">2</span>];</span><br><span class="line">            delta[q[<span class="number">1</span>] + <span class="number">1</span>] += q[<span class="number">2</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz; i++) &#123;</span><br><span class="line">            acc += delta[i];</span><br><span class="line">            <span class="keyword">if</span> (acc &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minZeroArray</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; queries)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 先检查</span></span><br><span class="line">        <span class="keyword">if</span>(<span class="built_in">isValid</span>(nums,queries,<span class="number">0</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> l = <span class="number">1</span>, r = queries.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> ans&#123;<span class="number">-1</span>&#125;;</span><br><span class="line">        <span class="keyword">while</span> (l &lt;= r) &#123;</span><br><span class="line">            <span class="type">int</span> m = (r - l) / <span class="number">2</span> + l;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isValid</span>(nums, queries, m)) &#123;</span><br><span class="line">                ans = m;</span><br><span class="line">                r = m - <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                l = m + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>航班预定统计</strong></p><p>这里有 <code>n</code> 个航班，它们分别从 <code>1</code> 到 <code>n</code> 进行编号。</p><p>有一份航班预订表 <code>bookings</code> ，表中第 <code>i</code> 条预订记录 <code>bookings[i] = [firsti, lasti, seatsi]</code> 意味着在从 <code>firsti</code> 到 <code>lasti</code> （<strong>包含</strong> <code>firsti</code> 和 <code>lasti</code> ）的 <strong>每个航班</strong> 上预订了 <code>seatsi</code> 个座位。</p><p>请你返回一个长度为 <code>n</code> 的数组 <code>answer</code>，里面的元素是每个航班预定的座位总数。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">corpFlightBookings</span><span class="params">(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; bookings, <span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 多个修改 一次查询</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">nums</span><span class="params">(n)</span></span>;</span><br><span class="line">        <span class="comment">// delta[i]表示bookings[i] - bookings[i-1]</span></span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">delta</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; p : bookings) &#123;</span><br><span class="line">            delta[p[<span class="number">0</span>] - <span class="number">1</span>] += p[<span class="number">2</span>];</span><br><span class="line">            delta[p[<span class="number">1</span>]] -= p[<span class="number">2</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            acc += delta[i];</span><br><span class="line">            nums[i] = acc;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nums;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>使数组互补的最少操作次数</p><p>给你一个长度为 <strong>偶数</strong> <code>n</code> 的整数数组 <code>nums</code> 和一个整数 <code>limit</code> 。每一次操作，你可以将 <code>nums</code> 中的任何整数替换为 <code>1</code> 到 <code>limit</code> 之间的另一个整数。</p><p>如果对于所有下标 <code>i</code>（<strong>下标从</strong> <code>0</code> <strong>开始</strong>），<code>nums[i] + nums[n - 1 - i]</code> 都等于同一个数，则数组 <code>nums</code> 是 <strong>互补的</strong> 。例如，数组 <code>[1,2,3,4]</code> 是互补的，因为对于所有下标 <code>i</code> ，<code>nums[i] + nums[n - 1 - i] = 5</code> 。</p><p>返回使数组 <strong>互补</strong> 的 <strong>最少</strong> 操作次数。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">minMoves</span><span class="params">(vector&lt;<span class="type">int</span>&gt;&amp; nums, <span class="type">int</span> limit)</span> </span>&#123;</span><br><span class="line">        <span class="function">vector&lt;<span class="type">int</span>&gt; <span class="title">arr</span><span class="params">(<span class="number">2</span> * limit + <span class="number">2</span>)</span></span>;</span><br><span class="line">        <span class="type">int</span> sz = nums.<span class="built_in">size</span>();</span><br><span class="line">        <span class="comment">// 2-2*limit</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; sz / <span class="number">2</span>; i++) &#123;</span><br><span class="line">            <span class="type">int</span> a = nums[i];</span><br><span class="line">            <span class="type">int</span> b = nums[sz - <span class="number">1</span> - i];</span><br><span class="line">            <span class="type">int</span> max_num = <span class="built_in">max</span>(a, b);</span><br><span class="line">            <span class="type">int</span> min_num = <span class="built_in">min</span>(a, b);</span><br><span class="line">            arr[<span class="number">2</span>] += <span class="number">2</span>;</span><br><span class="line">            arr[<span class="number">2</span> * limit + <span class="number">1</span>] -= <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">            arr[min_num + <span class="number">1</span>] -= <span class="number">1</span>;</span><br><span class="line">            arr[max_num + limit + <span class="number">1</span>] += <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">            arr[a + b] -= <span class="number">1</span>;</span><br><span class="line">            arr[a + b + <span class="number">1</span>] += <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> acc&#123;&#125;;</span><br><span class="line">        <span class="type">int</span> res&#123;sz&#125;;</span><br><span class="line">        <span class="comment">// arr[i]表示和为i的操作次数arr[i]</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">2</span>; i &lt; <span class="number">2</span> * limit + <span class="number">1</span>; i++) &#123;</span><br><span class="line">            acc += arr[i];</span><br><span class="line">            res = <span class="built_in">min</span>(acc, res);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>二维区域和检索-区域不可变</strong></p><p>给定一个二维矩阵 <code>matrix</code>，以下类型的多个请求：</p><ul><li>计算其子矩形范围内元素的总和，该子矩阵的 <strong>左上角</strong> 为 <code>(row1, col1)</code> ，<strong>右下角</strong> 为 <code>(row2, col2)</code> 。</li></ul><p>实现 <code>NumMatrix</code> 类：</p><ul><li><code>NumMatrix(int[][] matrix)</code> 给定整数矩阵 <code>matrix</code> 进行初始化</li><li><code>int sumRegion(int row1, int col1, int row2, int col2)</code> 返回 <strong>左上角</strong> <code>(row1, col1)</code> 、<strong>右下角</strong> <code>(row2, col2)</code> 所描述的子矩阵的元素 <strong>总和</strong> 。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">NumMatrix</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; prefixSum;</span><br><span class="line">    <span class="built_in">NumMatrix</span>(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix) &#123;</span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        prefixSum.<span class="built_in">resize</span>(<span class="number">1</span> + m);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            prefixSum[i].<span class="built_in">resize</span>(<span class="number">1</span> + n);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 前缀和</span></span><br><span class="line">        <span class="comment">// prefix[i][j]表示左上角mat[0][0]到右下角mat[i-1][j-1]的和</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                prefixSum[i][j] = prefixSum[i - <span class="number">1</span>][j] + prefixSum[i][j - <span class="number">1</span>] -</span><br><span class="line">                                  prefixSum[i - <span class="number">1</span>][j - <span class="number">1</span>] +</span><br><span class="line">                                  matrix[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">sumRegion</span><span class="params">(<span class="type">int</span> row1, <span class="type">int</span> col1, <span class="type">int</span> row2, <span class="type">int</span> col2)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> prefixSum[row2 + <span class="number">1</span>][col2 + <span class="number">1</span>] - prefixSum[row1][col2 + <span class="number">1</span>] -</span><br><span class="line">               prefixSum[row2 + <span class="number">1</span>][col1] + prefixSum[row1][col1];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Your NumMatrix object will be instantiated and called as such:</span></span><br><span class="line"><span class="comment"> * NumMatrix* obj = new NumMatrix(matrix);</span></span><br><span class="line"><span class="comment"> * int param_1 = obj-&gt;sumRegion(row1,col1,row2,col2);</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure><p><strong>二维区域和检索-矩阵可修改</strong></p><p>给你一个二维矩阵 <code>matrix</code> ，处理以下类型的多个查询:</p><ol><li><strong>更新</strong> <code>matrix</code> 中单元格的值。</li><li>计算由 <strong>左上角</strong> <code>(row1, col1)</code> 和 <strong>右下角</strong> <code>(row2, col2)</code> 定义的 <code>matrix</code> 内矩阵元素的 <strong>和</strong>。</li></ol><p>实现 <code>NumMatrix</code> 类：</p><ul><li><code>NumMatrix(int[][] matrix)</code> 用整数矩阵 <code>matrix</code> 初始化对象。</li><li><code>void update(int row, int col, int val)</code> <strong>更新</strong> <code>matrix[row][col]</code> 的值到 <code>val</code> 。</li><li><code>int sumRegion(int row1, int col1, int row2, int col2)</code> 返回矩阵 <code>matrix</code> 中指定矩形区域元素的 <strong>和</strong> ，该区域由 <strong>左上角</strong> <code>(row1, col1)</code> 和 <strong>右下角</strong> <code>(row2, col2)</code> 界定。</li></ul><p>由于涉及到<strong>频繁的更新（update）和频繁的查询（sumRegion）</strong>，我们不能使用静态的二维前缀和（因为更新一次需要 $O(n^2)$），也不能使用暴力遍历（因为查询一次需要 $O(n^2)$）。</p><p>最理想的方案是使用 <strong>二维树状数组（2D Binary Indexed Tree / Fenwick Tree）</strong>。它可以让更新和查询的时间复杂度都达到极佳的 $O(\log n \times \log m)$</p><p>树状数组（BIT）本质上是一种通过二进制分解来管理区间和的数据结构。在二维空间中，它相当于在行和列两个维度上各套了一层 BIT。</p><ul><li><strong><code>update(row, col, delta)</code></strong>：当某个点的值改变时，我们需要沿着 BIT 的路径向上更新所有受影响的“块”。</li><li><strong><code>query(row, col)</code></strong>：查询从 $(0, 0)$ 到 $(row, col)$ 的前缀和。我们需要沿着路径向下累加。</li></ul><p><strong>二维容斥原理查询</strong></p><p>当你有了 <code>query(row, col)</code> 函数后，计算任意矩形区域 $[row1, col1]$ 到 $[row2, col2]$ 的和，使用的正是你之前问过的那个<strong>容斥原理公式</strong>：</p><script type="math/tex; mode=display">\text{Sum} = \text{query}(r2, c2) - \text{query}(r1-1, c2) - \text{query}(r2, c1-1) + \text{query}(r1-1, c1-1)</script><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><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">NumMatrix</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 线段树范围更新</span></span><br><span class="line">    <span class="comment">// 线状数组单点更新，但可以多次进行批量更新</span></span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; tree;</span><br><span class="line">    vector&lt;vector&lt;<span class="type">int</span>&gt;&gt; nums;</span><br><span class="line">    <span class="type">int</span> m;</span><br><span class="line">    <span class="type">int</span> n;</span><br><span class="line">    <span class="built_in">NumMatrix</span>(vector&lt;vector&lt;<span class="type">int</span>&gt;&gt;&amp; matrix) &#123;</span><br><span class="line">        <span class="type">int</span> m = matrix.<span class="built_in">size</span>();</span><br><span class="line">        <span class="type">int</span> n = matrix[<span class="number">0</span>].<span class="built_in">size</span>();</span><br><span class="line">        <span class="keyword">this</span>-&gt;nums.<span class="built_in">resize</span>(m);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">this</span>-&gt;nums[i].<span class="built_in">resize</span>(n);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>-&gt;m = m;</span><br><span class="line">        <span class="keyword">this</span>-&gt;n = n;</span><br><span class="line">        tree.<span class="built_in">resize</span>(m + <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tree.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">            tree[i].<span class="built_in">resize</span>(n + <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 更新</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                <span class="built_in">update</span>(i - <span class="number">1</span>, j - <span class="number">1</span>, matrix[i - <span class="number">1</span>][j - <span class="number">1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">lowbit</span><span class="params">(<span class="type">int</span> x)</span> </span>&#123; <span class="keyword">return</span> x &amp; -x; &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col, <span class="type">int</span> val)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> delta = val - nums[row][col];</span><br><span class="line">        nums[row][col] = val;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = row + <span class="number">1</span>; i &lt;= m; i += <span class="built_in">lowbit</span>(i)) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = col + <span class="number">1</span>; j &lt;= n; j += <span class="built_in">lowbit</span>(j)) &#123;</span><br><span class="line">                tree[i][j] += delta;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">query</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col)</span> </span>&#123;</span><br><span class="line">        <span class="type">int</span> res&#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = row; i &gt;= <span class="number">1</span>; i -= <span class="built_in">lowbit</span>(i)) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = col; j &gt;= <span class="number">1</span>; j -= <span class="built_in">lowbit</span>(j)) &#123;</span><br><span class="line">                res += tree[i][j];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">sumRegion</span><span class="params">(<span class="type">int</span> row1, <span class="type">int</span> col1, <span class="type">int</span> row2, <span class="type">int</span> col2)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 前缀差</span></span><br><span class="line">        <span class="comment">// region = row2,col2 - row1,col2 - row2 col1 + row1,col1</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">query</span>(row2 + <span class="number">1</span>, col2 + <span class="number">1</span>) - <span class="built_in">query</span>(row1, col2 + <span class="number">1</span>) -</span><br><span class="line">               <span class="built_in">query</span>(row2 + <span class="number">1</span>, col1) + <span class="built_in">query</span>(row1, col1);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th><strong>工具</strong></th><th><strong>区间修改</strong></th><th><strong>区间查询</strong></th><th><strong>最佳场景</strong></th></tr></thead><tbody><tr><td><strong>前缀和数组</strong></td><td>$O(n)$</td><td>$O(1)$</td><td>数组不变，频繁查区间和</td></tr><tr><td><strong>差分数组</strong></td><td>$O(1)$</td><td>$O(n)$</td><td>频繁区间修改，最后统一查一次</td></tr><tr><td><strong>树状数组 / 线段树</strong></td><td>$O(\log n)$</td><td>$O(\log n)$</td><td>边修改边查询（动态维护）</td></tr></tbody></table></div><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>差分数组</strong></th><th><strong>树状数组 (BIT)</strong></th><th><strong>线段树</strong></th></tr></thead><tbody><tr><td><strong>主要功能</strong></td><td>区间修改</td><td>单点修改 + 前缀查询</td><td>区间修改 + 区间查询</td></tr><tr><td><strong>修改复杂度</strong></td><td>$O(1)$</td><td>$O(\log n)$</td><td>$O(\log n)$</td></tr><tr><td><strong>查询复杂度</strong></td><td>$O(n)$ (还原时)</td><td>$O(\log n)$</td><td>$O(\log n)$</td></tr><tr><td><strong>空间复杂度</strong></td><td>$O(n)$</td><td>$O(n)$</td><td>$O(4n)$</td></tr><tr><td><strong>代码量</strong></td><td>极少</td><td>较少</td><td>较多</td></tr><tr><td><strong>扩展性</strong></td><td>低</td><td>中</td><td>极高</td></tr></tbody></table></div><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/25/%E7%BA%BF%E6%AE%B5%E6%A0%91%E9%97%AE%E9%A2%98/</id>
    <link href="https://www.sekyoro.top/2026/01/25/%E7%BA%BF%E6%AE%B5%E6%A0%91%E9%97%AE%E9%A2%98/"/>
    <published>2026-01-25T15:42:06.000Z</published>
    <summary>
      <![CDATA[<p>线段树（Segment Tree）是处理区间问题的神器。如果你厌倦了 $O(N)$ 的暴力遍历，又觉得前缀和（Prefix Sum）处理不了动态修改，那么线段树就是那个能带你进入 $O(\log N)$ 世界的理想工具。<br>]]>
    </summary>
    <title>线段树问题</title>
    <updated>2026-02-09T03:59:15.421Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <category term="网站备份" scheme="https://www.sekyoro.top/tags/%E7%BD%91%E7%AB%99%E5%A4%87%E4%BB%BD/"/>
    <category term="运维" scheme="https://www.sekyoro.top/tags/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近阿里云服务器要一年续期了。云服务器商的潜规则就是割老韭菜，旧服务器的续期费用远多余重新买相同配置的服务器。新韭菜的服务器价格更低，所以不得不每一年换个服务器。这里记录一下备份操作。<br><span id="more"></span></p><h2 id="宝塔面板"><a href="#宝塔面板" class="headerlink" title="宝塔面板"></a>宝塔面板</h2><p>安装宝塔面板主要包括数据库,php网站(wordpress),ftp等数据。利用宝塔面板本身的网站迁移功能。</p><p><img data-src="https://s2.loli.net/2026/01/24/KNtkI96gF5P1ySf.png" alt="image-20260124190547334"></p><h2 id="安装的应用"><a href="#安装的应用" class="headerlink" title="安装的应用"></a>安装的应用</h2><p>除了wordpress网站之外，我也安装了一些非docker应用。</p><h3 id="filebrowser"><a href="#filebrowser" class="headerlink" title="filebrowser"></a>filebrowser</h3><p><a href="https://filebrowser.org/installation.html">filebrowser.org/installation.html</a></p><p>创建systemctl服务</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">[Unit]</span><br><span class="line">Description=File Browser</span><br><span class="line">After=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">ExecStart=/usr/local/bin/filebrowser \</span><br><span class="line">        -d /etc/filebrowser.db \</span><br><span class="line">        -p 9201 \</span><br><span class="line">        -a 0.0.0.0</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p>然后进行<strong>重载配置</strong>: <code>systemctl daemon-reload</code></p><p><strong>启动服务</strong>: <code>systemctl start filebrowser</code></p><p><strong>设置开机自启</strong>: <code>systemctl enable filebrowser</code></p><h2 id="ossfs挂载"><a href="#ossfs挂载" class="headerlink" title="ossfs挂载"></a>ossfs挂载</h2><p>由于云盘空间有限，默认40G，所以通过挂载oss空间进行扩展，也方便作为webdav和网盘存储空间。</p><p>之前服务使用的是ossfs1.x版本，目前2.x版本相较于ossfs 1.0在顺序读写和高并发小文件读取方面均实现了显著的性能提升。</p><ul><li><strong>顺序写入性能</strong>：在单线程大文件顺序写入场景下，ossfs 2.0的带宽相较于ossfs 1.0提升了近<strong>18倍</strong>。</li><li><strong>顺序读取性能：</strong>在单线程或多线程（4线程）大文件顺序读取场景下，ossfs 2.0的带宽相较于ossfs 1.0提升了<strong>3倍</strong>以上。</li><li><strong>小文件并发读取性能</strong>：在高并发（128线程）读取小文件的场景下，ossfs 2.0的带宽相较于ossfs 1.0提升了<strong>20倍</strong>以上。</li></ul><p>所以这里使用ossfs进行挂载。</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">sudo wget https://gosspublic.alicdn.com/ossfs/ossfs2_2.0.5_linux_x86_64.rpm</span><br><span class="line">sudo yum install ossfs2_2.0.5_linux_x86_64.rpm -y</span><br></pre></td></tr></table></figure><p>ossfs2.0的key配置,推荐还是使用ECSRAMRole,因为AccessKey容易过期。</p><p><img data-src="https://s2.loli.net/2026/01/24/HcoZD4TPdOjCupl.png" alt="image-20260124202954178"></p><h3 id="自动挂载"><a href="#自动挂载" class="headerlink" title="自动挂载"></a>自动挂载</h3><p><a href="https://help.aliyun.com/zh/oss/developer-reference/configure-auto-mount-on-for-ossfs-2-0?spm=a2c4g.11186623.help-menu-31815.d_1_2_6_6_3.133a6403cxpaL9">配置fstab使Linux重启后自动挂载OSS-对象存储-阿里云</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">ossfs2#</span><span class="language-bash">bucket /home/files fuse _netdev,nofail,x-systemd.after=networkd-dispatcher.service,conf=/etc/ossfs2.conf 0 0</span></span><br></pre></td></tr></table></figure><h2 id="docker应用"><a href="#docker应用" class="headerlink" title="docker应用"></a>docker应用</h2><p>重点是配置镜像源</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/docker/daemon.json</span><br></pre></td></tr></table></figure><figure class="highlight json"><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 class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;registry-mirrors&quot;</span> <span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">     <span class="string">&quot;https://docker.1ms.run&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://docker.m.daocloud.io&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://mirror.aliyuncs.com&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;insecure-registries&quot;</span> <span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;docker.mirrors.ustc.edu.cn&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;debug&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;experimental&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><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">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl restart docker</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --restart always --publish 9601:9601 -d proanimer/paper-reader:latest</span><br></pre></td></tr></table></figure><h3 id="webdav"><a href="#webdav" class="headerlink" title="webdav"></a>webdav</h3><p><a href="https://github.com/BytemarkHosting/docker-webdav">ker-webdav: Docker image for running an Apache WebDAV server</a></p><figure class="highlight shell"><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">docker run --restart always -v /srv/dav:/var/lib/dav \</span><br><span class="line">    -e AUTH_TYPE=Digest -e USERNAME=alice -e PASSWORD=secret1234 \</span><br><span class="line">    --publish 80:80 -d bytemark/webdav</span><br></pre></td></tr></table></figure><p>注意，这里的AUTH_TYPE是Digest,做反向代理时不方便。所以使用了Basic</p><h3 id="dillinger"><a href="#dillinger" class="headerlink" title="dillinger"></a>dillinger</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d -p 9501:8080 --restart=always --cap-add=SYS_ADMIN --name=dillinger proanimer/dillinger</span><br></pre></td></tr></table></figure><h3 id="静态网站反向代理"><a href="#静态网站反向代理" class="headerlink" title="静态网站反向代理"></a>静态网站反向代理</h3><p>有些静态页面也需要单独配置</p><figure class="highlight nginx"><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 class="section">location</span><span class="regexp"> ^~</span>  /navigator</span><br><span class="line">&#123;</span><br><span class="line">    <span class="attribute">alias</span> /naviweb/;</span><br><span class="line">    <span class="attribute">index</span> index.html;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">location</span><span class="regexp"> ^~</span>  /weather-app</span><br><span class="line">&#123;</span><br><span class="line">    <span class="attribute">alias</span> /home/staticsite/weather/;</span><br><span class="line">    <span class="attribute">index</span> index.html;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>此外，还有一些方便部署的应用<a href="https://github.com/awesome-selfhosted/awesome-selfhosted">awesome-selfhosted/awesome-selfhosted: A list of Free Software network services and web applications which can be hosted on your own servers</a></p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/24/%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E7%AB%99%E5%A4%87%E4%BB%BD%E5%AE%9E%E6%88%98/</id>
    <link href="https://www.sekyoro.top/2026/01/24/%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E7%AB%99%E5%A4%87%E4%BB%BD%E5%AE%9E%E6%88%98/"/>
    <published>2026-01-24T10:42:32.000Z</published>
    <summary>
      <![CDATA[<p>最近阿里云服务器要一年续期了。云服务器商的潜规则就是割老韭菜，旧服务器的续期费用远多余重新买相同配置的服务器。新韭菜的服务器价格更低，所以不得不每一年换个服务器。这里记录一下备份操作。<br>]]>
    </summary>
    <title>服务器网站备份实战</title>
    <updated>2026-01-31T12:04:44.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>已经使用latex写论文一段时间了，但对于cls模板还是缺乏认识。这里尝试从基础命令开始写一个简单模板。<br><span id="more"></span></p><p>当我们撰写一个Latex文档时，可能会使用.cls以及.sty包,在 LaTeX 体系中，<code>.cls</code> 和 <code>.sty</code> 是两类地位完全不同、但经常配合使用的文件。Latex的模板就捆绑了 <strong>class</strong> (<code>.cls</code>)、<strong>style</strong> (<code>.sty</code>)、示例 <code>.tex</code> 文件和支持资源(徽标、参考书目文件等)，这些资源定义了特定文档类型(期刊文章、论文、简历、海报、幻灯片等)的布局、排版和结构。</p><p><code>.cls</code> 定义的是 <strong>文档的“身份”和全局结构”</strong>，例如：</p><ul><li>单栏 / 双栏</li><li>页面尺寸、边距</li><li>字号体系</li><li>章节结构（section / subsection）</li><li>浮动体整体规则（figure / table）</li></ul><p><code>.sty</code> 是 <strong>“功能或样式插件”</strong>，用于<strong>扩展或局部修改 LaTeX 的能力</strong>。</p><p>一个tex文件内容如下</p><figure class="highlight tex"><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"><span class="keyword">\documentclass</span>[options]&#123;mytemplate&#125;   <span class="comment">% class supplied by the template</span></span><br><span class="line"><span class="keyword">\usepackage</span>&#123;mytemplate&#125;               <span class="comment">% sometimes a .sty instead of .cls</span></span><br><span class="line"><span class="keyword">\begin</span>&#123;document&#125;</span><br><span class="line">  <span class="keyword">\title</span>&#123;My Title&#125;</span><br><span class="line">  <span class="keyword">\author</span>&#123;Me&#125;</span><br><span class="line">  <span class="keyword">\maketitle</span></span><br><span class="line">  <span class="comment">% &lt;content&gt;</span></span><br><span class="line"><span class="keyword">\end</span>&#123;document&#125;</span><br></pre></td></tr></table></figure><p>创建自己的模板需要同时使用cls类完全控制文档结构，同时使用stl样式文件包含额外的宏或者格式。编写cls的方式一是可以通过编写dtx文件另一个是直接基于现有cls模板进行调整。考虑到常见情况，其实基于cls模板进行更改更实际。</p><div class="table-container"><table><thead><tr><th>LaTeX</th><th>软件工程</th></tr></thead><tbody><tr><td>dtx</td><td>source code</td></tr><tr><td>ins</td><td>build script</td></tr><tr><td>cls/sty</td><td>binary</td></tr><tr><td>pdf</td><td>documentation</td></tr></tbody></table></div><h2 id="dtx文件"><a href="#dtx文件" class="headerlink" title="dtx文件"></a>dtx文件</h2><p>而dtx本质是文学化编程(literate programming)+docstrip.</p><p>一个标准 dtx 文件 = 3 个部分：</p><ol><li>driver（生成文档）</li><li>documentation（说明文档）</li><li>tagged code（可提取代码）</li></ol><p>文学化编程含义就是在源代码中包含注解并且生成的内容也会包含注解pdf,让人看dtx源码就能容易看懂。</p><h3 id="dtx包含内容"><a href="#dtx包含内容" class="headerlink" title="dtx包含内容"></a>dtx包含内容</h3><h4 id="driver部分"><a href="#driver部分" class="headerlink" title="driver部分"></a>driver部分</h4><p>driver 部分主要用于 <strong>生成 PDF 文档</strong>，也就是文档化源码（ltxdoc 风格）：</p><ul><li>显示文档说明文字</li><li>显示代码块示例</li><li>可以生成宏索引、代码行索引等</li><li><strong>不执行 cls/sty 的核心功能</strong>（否则可能报错）</li></ul><blockquote><p>核心目标：把 dtx 文件生成 <strong>可读的文档</strong>。作用：生成<strong>说明文档</strong> PDF。</p></blockquote><figure class="highlight livescript"><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></pre></td><td class="code"><pre><span class="line">% <span class="string">\iffalse</span></span><br><span class="line">%&lt;*driver&gt;</span><br><span class="line"><span class="string">\documentclass&#123;ltxdoc&#125;</span></span><br><span class="line"><span class="string">\EnableCrossrefs</span></span><br><span class="line"><span class="string">\CodelineIndex</span></span><br><span class="line"><span class="string">\RecordChanges</span></span><br><span class="line"><span class="string">\begin&#123;document&#125;</span></span><br><span class="line"><span class="string">\DocInput&#123;myclass.dtx&#125;</span></span><br><span class="line"><span class="string">\end&#123;document&#125;</span></span><br><span class="line">%&lt;/driver&gt;</span><br><span class="line">% <span class="string">\fi</span></span><br></pre></td></tr></table></figure><p><code>% \iffalse ... % \fi</code><br>  防止 driver 被 docstrip 提取</p><p><code>&lt;*driver&gt;</code> tag<br> 只用于文档，不用于 cls</p><p><strong><code>\EnableCrossrefs</code></strong> 和 <strong><code>\CodelineIndex</code></strong>：自动为宏包生成代码索引。</p><p><strong><code>\PrintChanges</code></strong>：根据 <code>% \changes</code> 记录自动生成修订历史，这对大型项目</p><p><strong><code>\ProvidesFile{latex.dtx}</code></strong>：向 LaTeX 系统声明当前文件的名称。</p><p><strong><code>\documentclass{ltxdoc}</code></strong>：加载 LaTeX 官方专门为编写宏包文档设计的 <strong><code>ltxdoc</code></strong> 文档类。它提供了诸如列出宏定义、生成索引等特殊功能。</p><p><strong><code>\usepackage{hypdoc}</code></strong>：为文档添加超链接支持（基于 <code>hyperref</code>），并针对文档中的宏和索引进行了优化。</p><p><strong><code>\EnableCrossrefs</code></strong>：开启宏定义的交叉引用功能，自动记录每个宏在代码中的位置。</p><p><strong><code>\CodelineIndex</code></strong>：设置索引以<strong>行号</strong>为基准，而不是页码，方便读者精确定位代码。</p><p><strong><code>\RecordChanges</code></strong>：开启变更记录功能，配合下文的 <code>\changes</code> 命令生成版本修订历史。</p><p>开头的 <code>%&lt;*driver&gt;</code> 部分是告诉 LaTeX 如何编译这份文档。它通常加载 <code>ltxdoc</code> 类，这是专门为记录 LaTeX 宏包设计的类。</p><p><strong><code>macrocode</code> 环境</strong>：这是 <code>.dtx</code> 最特殊的地方。它内部的代码会被提取到 <code>.sty</code> 或 <code>.cls</code> 文件中，而在 PDF 文档里则会带有行号显示。也就是对于pdf排版和cls文件都有用，主要是告诉 PDF 排版引擎“这是代码，请原样打印并加行号”。<strong>提取为cls</strong>：必须配合 <strong>Guard Tags</strong>（如 <code>%&lt;*...&gt;</code>）且在 <strong><code>.ins</code> 文件</strong>中正确配置，内容才会进入 <code>.cls</code>。<strong>ltxdoc 类的核心行为</strong></p><p>dtx 文件的 driver 部分通常是：</p><figure class="highlight dust"><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></pre></td><td class="code"><pre><span class="line"><span class="language-xml">\documentclass</span><span class="template-variable">&#123;ltxdoc&#125;</span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">\begin</span><span class="template-variable">&#123;document&#125;</span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">\DocInput</span><span class="template-variable">&#123;myclass.dtx&#125;</span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">\end</span><span class="template-variable">&#123;document&#125;</span></span><br></pre></td></tr></table></figure><p>ltxdoc 的会把 dtx 文件中 <strong>特殊注释</strong>（如 <code>% \section{…}</code>、<code>% \begin{macro}{…}</code>）识别为文档命令，而不是普通注释，<code>%</code> 只是标记注释行，driver 编译时会 <strong>去掉前导 <code>%</code></strong> 并执行其中的 LaTeX 文档命令。</p><p><code>\begin{macro}</code>当读者快速翻页查找某个特定宏的实现时，眼睛只需要盯着左侧边缘的标签，而不需要阅读中间密集的代码。</p><p>ltxdoc 会区分：</p><div class="table-container"><table><thead><tr><th>类型</th><th>编译结果</th><th>注释方式</th></tr></thead><tbody><tr><td>文档文字</td><td>PDF 普通文本</td><td><code>%</code> 开头普通文字</td></tr><tr><td>文档命令</td><td>PDF 按样式渲染</td><td><code>%</code> 开头，但行中有 <code>\section</code>, <code>\subsection</code>, <code>\begin{macro}</code> 等</td></tr><tr><td>宏代码</td><td>PDF 代码块显示</td><td><code>% \begin{macrocode} … % \end{macrocode}</code></td></tr></tbody></table></div><p>核心规律：<strong>ltxdoc 会自动识别 <code>%</code> 注释中的 LaTeX 文档命令并执行它来生成 PDF 样式，而普通文字不会被执行</strong></p><h4 id="documentation部分"><a href="#documentation部分" class="headerlink" title="documentation部分"></a>documentation部分</h4><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">% \section&#123;Introduction&#125;</span></span><br><span class="line"><span class="comment">% This is my custom class.</span></span><br><span class="line"><span class="comment">%</span></span><br><span class="line"><span class="comment">% \subsection&#123;Features&#125;</span></span><br><span class="line"><span class="comment">% - Custom section style</span></span><br><span class="line"><span class="comment">% - Custom layout</span></span><br></pre></td></tr></table></figure><p>规则：</p><ul><li>每行必须以 <code>%</code> 开头</li><li>可以写完整 LaTeX 文档</li><li>会出现在 PDF 文档中</li></ul><h4 id="tagged-code部分"><a href="#tagged-code部分" class="headerlink" title="tagged code部分"></a>tagged code部分</h4><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">%&lt;*class&gt;</span></span><br><span class="line"><span class="keyword">\NeedsTeXFormat</span>&#123;LaTeX2e&#125;</span><br><span class="line"><span class="keyword">\ProvidesClass</span>&#123;myclass&#125;[2026/01/22 My class]</span><br><span class="line"><span class="keyword">\LoadClass</span>&#123;article&#125;</span><br><span class="line"><span class="comment">%&lt;/class&gt;</span></span><br></pre></td></tr></table></figure><p><code>%&lt;*tag&gt;</code> 开始</p><p><code>%&lt;/tag&gt;</code> 结束</p><p>中间是纯 TeX 代码（不能加 % 注释）。tagged code 是 <strong>源码块</strong>，可以被 docstrip 提取生成可执行的 cls/sty 文件，同时也可以用来生成 PDF 中的代码显示：</p><ul><li>定义 class/package 的宏命令、环境、选项</li><li>可以写文档注释（% 开头）用于 PDF 文档显示</li><li>提供 <strong>可控模块化</strong>（通过 tag 提取不同内容生成不同文件）核心目标：把源码和文档组织在同一个文件，方便提取和展示。</li></ul><blockquote><p><code>\begin{macro}</code> 用来<strong>文档化一个宏（命令）</strong>，并生成“宏索引”和结构化说明。</p></blockquote><p>它的主要作用是：</p><ol><li>给某个宏建立“文档条目”</li><li>在 PDF 中生成“宏说明块”</li><li>在索引中登记宏名（配合 <code>\CodelineIndex</code>）</li><li>让 dtx 文档变得像 API 文档</li></ol><blockquote><p>宏就是“自定义命令”。比如：<code>\newcommand{\hello}{Hello}</code></p></blockquote><h4 id="模块化设计"><a href="#模块化设计" class="headerlink" title="模块化设计"></a>模块化设计</h4><figure class="highlight tex"><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 class="comment">%&lt;*base&gt;</span></span><br><span class="line">基础宏定义</span><br><span class="line"><span class="comment">%&lt;/base&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">%&lt;*layout&gt;</span></span><br><span class="line">版式控制</span><br><span class="line"><span class="comment">%&lt;/layout&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">%&lt;*section&gt;</span></span><br><span class="line">章节结构</span><br><span class="line"><span class="comment">%&lt;/section&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">%&lt;*option&gt;</span></span><br><span class="line">class options</span><br><span class="line"><span class="comment">%&lt;/option&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">%&lt;*class&gt;</span></span><br><span class="line">主类文件</span><br><span class="line"><span class="comment">%&lt;/class&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>简单来说,<code>%</code>开头的都会在pdf中显示,除非tagged code块中除开%后是<code>\指令</code>则会执行，如果不是%开头，则会出现在cls文件中，如果是在tagged code中类似。</p><p>docstrip 会把 <strong>非注释行（不以 <code>%</code> 开头）的内容</strong> 当作 <strong>真正源码</strong>。也就是说，无论它在 <code>%&lt;*tag&gt;</code> 中还是不在，docstrip 都会把这些行写进目标文件。有了 tag，你可以控制 docstrip 只提取 <code>class</code> 或 <code>macro</code>如果不使用 tag，所有非 <code>%</code> 行都会默认被提取到生成文件</p><h3 id="如何让dtx生成cls"><a href="#如何让dtx生成cls" class="headerlink" title="如何让dtx生成cls"></a>如何让dtx生成cls</h3><p>使用dtx生成cls文件流程：</p><ol><li>写 dtx</li><li>写 ins</li><li>运行 ins</li></ol><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></pre></td><td class="code"><pre><span class="line">% \iffalse meta-comment</span><br><span class="line">% \iffalse</span><br><span class="line">%&lt;*driver&gt;</span><br><span class="line">\documentclass&#123;ltxdoc&#125;</span><br><span class="line">\begin&#123;document&#125;</span><br><span class="line">\DocInput&#123;myclass.dtx&#125;</span><br><span class="line">\end&#123;document&#125;</span><br><span class="line">%&lt;/driver&gt;</span><br><span class="line">% \fi</span><br><span class="line"></span><br><span class="line">% \section&#123;MyClass&#125;</span><br><span class="line">% This is a demo class.</span><br><span class="line"></span><br><span class="line">%&lt;*class&gt;</span><br><span class="line">\NeedsTeXFormat&#123;LaTeX2e&#125;</span><br><span class="line">\ProvidesClass&#123;myclass&#125;[2026/01/22 Demo class]</span><br><span class="line">\LoadClass&#123;article&#125;</span><br><span class="line">%&lt;/class&gt;</span><br></pre></td></tr></table></figure><p><code>% \iffalse meta-comment</code> 是实现“<strong>文学编程</strong>”的一种核心机制。它允许你将排版文档所需的指令（如编译说明和驱动程序）隐藏起来，不让它们出现在最终提取的宏包代码中。</p><p>而ins文件是”安装文件”，需要利用ins文件生成cls</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">\input docstrip.tex</span><br><span class="line"></span><br><span class="line">\generate&#123;</span><br><span class="line">  \file&#123;myclass.cls&#125;&#123;\from&#123;myclass.dtx&#125;&#123;class&#125;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">\endbatchfile</span><br></pre></td></tr></table></figure><p>最后运行命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">latex myclass.ins</span><br></pre></td></tr></table></figure><p>其中安装文件使用了docstrip生成cls.所以需要利用ins生成cls文件,如果直接执行<code>latex *.dtx</code>,执行 dtx 时：</p><blockquote><p>driver 会被执行<br> documentation 会被排版进 PDF<br>  tagged code 不会被提取<br> 不会生成 cls/sty</p></blockquote><p>换句话说：</p><blockquote><p><code>.dtx</code> 被当成“说明文档”编译，而不是“源码”。</p></blockquote><figure class="highlight stylus"><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></pre></td><td class="code"><pre><span class="line">xelatex latex<span class="selector-class">.dtx</span></span><br><span class="line">makeindex -s gind<span class="selector-class">.ist</span> -o latex<span class="selector-class">.ind</span> latex<span class="selector-class">.idx</span></span><br><span class="line">makeindex -s gglo<span class="selector-class">.ist</span> -o latex<span class="selector-class">.gls</span> latex<span class="selector-class">.glo</span></span><br><span class="line">xelatex latex.dtx</span><br></pre></td></tr></table></figure><p>如果使用了中文等，可以考虑xelatex</p><h2 id="cls文件"><a href="#cls文件" class="headerlink" title="cls文件"></a>cls文件</h2><p>cls文件格式往往如下。</p><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">% mytemplate.cls – a very simple article‑style class</span></span><br><span class="line"><span class="keyword">\NeedsTeXFormat</span>&#123;LaTeX2e&#125;</span><br><span class="line"><span class="keyword">\ProvidesClass</span>&#123;mytemplate&#125;[2025/12/03 v1.0 My custom class]</span><br><span class="line"><span class="keyword">\LoadClass</span>[12pt]&#123;article&#125;   <span class="comment">% inherit from article</span></span><br><span class="line"></span><br><span class="line"><span class="comment">% ---- custom settings ----</span></span><br><span class="line"><span class="keyword">\RequirePackage</span>&#123;geometry&#125;</span><br><span class="line"><span class="keyword">\geometry</span>&#123;margin=2cm&#125;</span><br><span class="line"><span class="keyword">\RequirePackage</span>&#123;fontspec&#125;</span><br><span class="line"><span class="keyword">\setmainfont</span>&#123;Latin Modern Roman&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">% ---- user‑level commands ----</span></span><br><span class="line"><span class="keyword">\newcommand</span><span class="keyword">\mytitle</span>[1]&#123;<span class="keyword">\centerline</span>&#123;<span class="keyword">\LARGE</span><span class="keyword">\bfseries</span> <span class="params">#1</span>&#125;&#125;</span><br><span class="line"><span class="keyword">\endinput</span></span><br></pre></td></tr></table></figure><p>cls文件底层是Tex,而不是Latex,其核心问题包含三个：</p><ol><li>文档类如何加载？</li><li>类如何定义结构和命令？</li><li>类如何控制版式？</li></ol><p>下面围绕这三点说明。</p><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">\NeedsTeXFormat</span>&#123;LaTeX2e&#125;</span><br><span class="line"><span class="keyword">\ProvidesClass</span>&#123;myclass&#125;[2026/01/22 My custom class]</span><br><span class="line"></span><br><span class="line"><span class="keyword">\LoadClass</span>&#123;article&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>\NeedsTeXFormat</code></td><td>声明 LaTeX 版本</td></tr><tr><td><code>\ProvidesClass</code></td><td>声明类信息</td></tr><tr><td><code>\LoadClass</code></td><td>继承已有类</td></tr><tr><td><code>\RequirePackage</code></td><td>加载宏包</td></tr></tbody></table></div><h2 id="宏定义"><a href="#宏定义" class="headerlink" title="宏定义"></a>宏定义</h2><p>LaTeX 中的宏定义分为：Tex原生宏以及Latex宏接口。</p><p>来自 TeX 引擎,cls/sty内部大量使用</p><ul><li><code>\def</code></li><li><code>\edef</code></li><li><code>\xdef</code></li><li><code>\gdef</code></li><li><code>\let</code></li><li><code>\futurelet</code></li></ul><p>来自 LaTeX,用户和宏包作者推荐使用</p><ul><li><code>\newcommand</code></li><li><code>\renewcommand</code></li><li><code>\providecommand</code></li><li><code>\DeclareRobustCommand</code></li><li><code>\NewDocumentCommand</code>（xparse）</li><li><code>\NewExpandableDocumentCommand</code></li></ul><p>最常用的就是<code>\def</code>和<code>\newcommand</code>,</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\def</span><span class="keyword">\foo</span>&#123;Hello&#125;</span><br></pre></td></tr></table></figure><p>特点：</p><p>不检查是否已定义（会覆盖）</p><p>语法灵活</p><p>可定义复杂参数模式</p><p>\newcommand（LaTeX 推荐）</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\newcommand</span>&#123;<span class="keyword">\foo</span>&#125;&#123;Hello&#125;</span><br></pre></td></tr></table></figure><p>特点：</p><ul><li>如果宏已存在 → 报错</li><li>语法安全</li><li>参数规则固定</li></ul><p>另外还有 \renewcommand（重新定义）</p><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\renewcommand&#123;\section&#125;<span class="meta">&#123;...&#125;</span></span><br></pre></td></tr></table></figure><p>如果宏不存在 → 报错。</p><p>\providecommand（如果不存在才定义）</p><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml">\providecommand</span><span class="template-variable">&#123;\foo&#125;</span><span class="template-variable">&#123;Hello&#125;</span></span><br></pre></td></tr></table></figure><p>逻辑：</p><ul><li>如果 \foo 已存在 → 什么都不做</li><li>如果不存在 → 定义</li></ul><div class="table-container"><table><thead><tr><th>类别</th><th>命令</th><th>作用</th></tr></thead><tbody><tr><td>TeX</td><td>\def</td><td>定义宏</td></tr><tr><td>TeX</td><td>\gdef</td><td>全局宏</td></tr><tr><td>TeX</td><td>\edef</td><td>展开定义</td></tr><tr><td>TeX</td><td>\xdef</td><td>全局展开</td></tr><tr><td>TeX</td><td>\let</td><td>赋值宏</td></tr><tr><td>LaTeX</td><td>\newcommand</td><td>新宏</td></tr><tr><td>LaTeX</td><td>\renewcommand</td><td>重定义</td></tr><tr><td>LaTeX</td><td>\providecommand</td><td>安全定义</td></tr><tr><td>LaTeX</td><td>\DeclareRobustCommand</td><td>鲁棒宏</td></tr><tr><td>LaTeX</td><td>\NewDocumentCommand</td><td>现代宏</td></tr><tr><td>LaTeX</td><td>\NewExpandableDocumentCommand</td><td>可展开宏</td></tr></tbody></table></div><p>\newcommand</p><p>\renewcommand</p><p>\def</p><p>\let</p><p>\DeclareRobustCommand</p><p>\NewDocumentCommand</p><h4 id="常用宏"><a href="#常用宏" class="headerlink" title="常用宏"></a>常用宏</h4><p><code>\newifif</code></p><p><code>\newif</code> 用来<strong>创建一个布尔型开关宏</strong>（true/false），通常用于：</p><ul><li>选项开关（class/package options）</li><li>控制是否执行某段代码</li></ul><p>它会生成三个宏：</p><ol><li><code>\&lt;name&gt;true</code> — 设置开关为真</li><li><code>\&lt;name&gt;false</code> — 设置开关为假</li><li><code>\&lt;name&gt;?</code> — 查询开关状态（返回 true/false 可用在 if 语句中）</li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\newif\if<span class="tag">&lt;<span class="name">name</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li><code>&lt;name&gt;</code>：开关名称（不带 <code>if</code> 前缀）</li><li>自动生成 <code>\if&lt;name&gt;</code> 条件宏</li></ul><p>例子：</p><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">\newif\ifdraft</span></span><br></pre></td></tr></table></figure><p>生成：</p><ul><li><code>\ifdraft</code> → 条件宏</li><li><code>\drafttrue</code> → 将 <code>\ifdraft</code> 设置为真</li><li><code>\draftfalse</code> → 将 <code>\ifdraft</code> 设置为假</li></ul><p><code>\def</code>重定义宏</p><h2 id="有了基础之后"><a href="#有了基础之后" class="headerlink" title="有了基础之后"></a>有了基础之后</h2><p>所谓文学编程，本质还是利用%表示注解并可以输出到pdf中供使用者查看，而不使用%可以输出到cls源码进行构建。因为dtx文件本质包含了cls文件和可以导出pdf的注释，所以我们直接看cls文件。</p><p>以一个硕士毕业论文模板讨论。<a href="https://github.dev/drowning-in-codes/Yet-Another-LaTeX-Template-for-NPU-Thesis">yanputhesis.cls</a></p><figure class="highlight tex"><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"><span class="keyword">\NeedsTeXFormat</span>&#123;LaTeX2e&#125;[2005/12/01]</span><br><span class="line"><span class="keyword">\ProvidesClass</span>&#123;yanputhesis&#125;</span><br><span class="line">    [2023/03/07 v1.8.5 Yet Another NPU Thesis Template]</span><br></pre></td></tr></table></figure><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\NeedsTeXFormat&#123;LaTeX2e&#125;<span class="string">[2005/12/01]</span></span><br></pre></td></tr></table></figure><ul><li><strong>作用</strong>：指定运行该文件所需的最低格式版本。</li><li><strong>含义</strong>：它告诉编译器，该模板必须在核心宏包版本日期不能早于 <strong>2005年12月1日</strong>。如果用户使用的系统过于陈旧，编译器会发出警告。</li></ul><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml">\ProvidesClass</span><span class="template-variable">&#123;yanputhesis&#125;</span></span><br></pre></td></tr></table></figure><ul><li><strong>作用</strong>：向系统声明这个文件的名称。</li><li><strong>重要性</strong>：这个名字必须与你文件夹中的文件名 <strong>严格一致</strong>（即文件名必须是 <code>yanputhesis.cls</code>）。当用户在 <code>.tex</code> 主文件中写下 <code>\documentclass{yanputhesis}</code> 时，系统就是通过这个命令来确认“找对人了”。</li></ul><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\RequirePackage</span>&#123;xkeyval&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>作用</strong>：引入高级参数处理宏包。</li><li><strong>意义</strong>：原生的 LaTeX 只支持简单的 <code>[option]</code>。使用 <code>xkeyval</code> 后，模板可以处理像 <code>type=phd</code> 或 <code>font=adobe</code> 这样“键-值对”格式的复杂选项。</li></ul><p>定义状态开关 (<code>\newif</code>)</p><p>这一部分定义了大量的布尔变量（即只有“真”或“假”两种状态的开关）。</p><blockquote><p><strong>注意</strong>：命令中包含 <code>@</code> 符号，说明这些是<strong>内部命令</strong>，防止普通用户在正文中误改。</p></blockquote><div class="table-container"><table><thead><tr><th><strong>命令</strong></th><th><strong>对应功能</strong></th><th><strong>默认含义</strong></th></tr></thead><tbody><tr><td><code>\if@npu@lang@chs</code></td><td><strong>语言环境</strong></td><td>如果为真，则排版中文版；为假则排版英文版。</td></tr><tr><td><code>\if@npu@type@phd</code></td><td><strong>博士学位</strong></td><td>标记当前是否为博士论文。</td></tr><tr><td><code>\if@npu@type@mst</code></td><td><strong>硕士学位</strong></td><td>标记当前是否为硕士论文。</td></tr><tr><td><code>\if@npu@type@bcl</code></td><td><strong>本科毕业设计</strong></td><td>标记当前是否为本科生论文。</td></tr><tr><td><code>\if@npu@academic</code></td><td><strong>学术/专业</strong></td><td>用于区分“学术学位”和“专业学位”（工程硕士等）。</td></tr><tr><td><code>\if@npu@output@blindreview</code></td><td><strong>盲评模式</strong></td><td><strong>重要开关</strong>。开启后会隐藏作者姓名、导师等敏感信息。</td></tr><tr><td><code>\if@npu@font@adobe</code></td><td><strong>Adobe 字体</strong></td><td>是否使用 Adobe 系列字体进行排版。</td></tr><tr><td><code>\if@npu@font@winfonts</code></td><td><strong>Windows 字体</strong></td><td>是否强制调用 Windows 系统内置字体（如中易宋体）。</td></tr></tbody></table></div><p><code>\def</code> 定义的宏是用来具体“拨动”上面那些开关的。最巧妙的是<strong>互斥（Mutex）</strong>逻辑：</p><p>代码段</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\def</span><span class="keyword">\set@type@phd</span>&#123; <span class="keyword">\@npu@type@phdtrue</span> <span class="keyword">\@npu@type@mstfalse</span> <span class="keyword">\@npu@type@bclfalse</span>&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>逻辑解析</strong>：当你设置论文类型为“博士（phd）”时，程序会自动将 <code>phd</code> 设为 <code>true</code>，同时<strong>强制</strong>将 <code>mst</code>（硕士）和 <code>bcl</code>（本科）设为 <code>false</code>。</li><li><strong>目的</strong>：防止用户由于误操作同时开启了多个学位类型，导致封面排版冲突。</li></ul><p>当你写下： <code>\documentclass[type=phd, lang=chs]{yanputhesis}</code></p><ol><li><code>xkeyval</code> 会解析出 <code>type=phd</code>。</li><li>模板内部会调用 <code>\set@type@phd</code>。</li><li>随后模板会根据 <code>\if@npu@type@phd</code> 这个开关是否为真，来决定封面上打印“<strong>博士学位论文</strong>”还是“<strong>硕士学位论文</strong>”。</li></ol><p>代码中多次出现的 <code>\csname ... \endcsname</code> 是底层命令，用于<strong>将字符串动态转换为命令名</strong>。</p><ul><li><strong>示例</strong>：<code>\DeclareOptionX{lang}[chs]{\csname set@lang@#1\endcsname}</code><ul><li>如果用户写 <code>lang=eng</code>，<code>#1</code> 就是 <code>eng</code>。</li><li>系统会自动拼接成 <code>\set@lang@eng</code> 并执行，从而调用你上一段代码中定义的语言设置宏。</li></ul></li></ul><p>使用模板,其中lang=chs等选项就是通过<code>\DeclareOptionX{键名}[默认值]{执行代码}</code>设置的</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\documentclass</span>[lang=chs, degree=phd, blindreview=false, winfonts=true, academic=true]&#123;yanputhesis&#125;</span><br></pre></td></tr></table></figure><figure class="highlight tex"><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="keyword">\DeclareOptionX</span>&#123;lang&#125;[chs]&#123;<span class="keyword">\csname</span> set@lang@<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;degree&#125;[phd]&#123;<span class="keyword">\csname</span> set@type@<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;blindreview&#125;[true]&#123;<span class="keyword">\csname</span> @npu@output@blindreview<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;adobe&#125;[true]&#123;<span class="keyword">\csname</span> @npu@font@adobe<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;winfonts&#125;[true]&#123;<span class="keyword">\csname</span> @npu@font@winfonts<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;academic&#125;[true]&#123;<span class="keyword">\csname</span> @npu@academic<span class="params">#1</span><span class="keyword">\endcsname</span>&#125;</span><br><span class="line"><span class="keyword">\DeclareOptionX</span>*&#123;<span class="keyword">\PassOptionsToClass</span>&#123;<span class="keyword">\CurrentOption</span>&#125;&#123;book&#125;&#125; <span class="comment">% 传递参数到 book 类</span></span><br></pre></td></tr></table></figure><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\DeclareOptionX</span>&#123;键名&#125;[默认值]&#123;执行代码&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>键名 (Key)</strong>：用户在 <code>\documentclass[...]</code> 中写的参数名（如 <code>lang</code>、<code>degree</code>）。</li><li><strong>默认值 (Default)</strong>：如果用户只写了键名而没给值（例如只写 <code>[lang]</code> 而非 <code>[lang=chs]</code>），系统会自动套用的值。</li><li><strong>执行代码</strong>：当该选项被激活时运行的 LaTeX 指令。在代码中，<strong><code>#1</code></strong> 代表用户输入的数据。</li></ul><p><code>\ExecuteOptionsX</code>这些命令的作用是设定<strong>默认值</strong>。如果在 <code>\documentclass{yanputhesis}</code> 中没有输入任何参数，模板将自动按照以下配置运行</p><p><strong><code>\ProcessOptionsX \relax</code></strong> 这是整个参数解析流程的“开关”。它会对比用户在文档开头输入的参数与上述默认值，最终决定当前编译环境的各项指标。</p><p><strong><code>\LoadClass[...] {book}</code></strong> 这行代码表明 <code>yanputhesis</code> 是<strong>继承自基础 <code>book</code> 类</strong>开发的</p><p>所以上面的代码通过<code>DecareOptionX</code>声明选项,并设置变量,方便后续通过变量设置属性。</p><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">\newcommand</span><span class="keyword">\@npu@replaceitwithblank</span>[1]&#123;&#123;<span class="keyword">\setlength</span>&#123;<span class="comment">%        % 替换成相同宽度空白</span></span><br><span class="line">                <span class="keyword">\fboxsep</span>&#125;&#123;0pt&#125;<span class="keyword">\colorbox</span>&#123;white&#125;&#123;<span class="keyword">\phantom</span>&#123;<span class="params">#1</span>&#125;&#125;&#125;&#125;</span><br><span class="line"><span class="keyword">\newcommand</span><span class="keyword">\@npu@replaceitwithblack</span>[1]&#123;&#123;<span class="keyword">\setlength</span>&#123;<span class="comment">%        % 替换成涂黑方块</span></span><br><span class="line">                <span class="keyword">\fboxsep</span>&#125;&#123;0pt&#125;<span class="keyword">\colorbox</span>&#123;black&#125;&#123;<span class="keyword">\phantom</span>&#123;<span class="params">#1</span>&#125;&#125;&#125;&#125;</span><br><span class="line"><span class="keyword">\newcommand</span><span class="keyword">\@npu@replaceitwithstars</span>&#123; *** &#125;                  <span class="comment">% 替换成 3 个星号</span></span><br><span class="line"><span class="keyword">\newcommand</span>&#123;<span class="keyword">\blindreview</span>&#125;[1]&#123;<span class="keyword">\if@npu@output@blindreview</span><span class="comment">%    % 空白盲评标记</span></span><br><span class="line">        <span class="keyword">\@npu@replaceitwithblank</span>&#123;<span class="params">#1</span>&#125;<span class="keyword">\relax</span><span class="keyword">\else</span> <span class="params">#1</span><span class="keyword">\fi</span>&#125;      <span class="comment">%</span></span><br><span class="line"><span class="keyword">\newcommand</span>&#123;<span class="keyword">\blackbox</span>&#125;[1]&#123;<span class="keyword">\if@npu@output@blindreview</span><span class="comment">%       % 涂黑盲评标记</span></span><br><span class="line">        <span class="keyword">\@npu@replaceitwithblack</span>&#123;<span class="params">#1</span>&#125;<span class="keyword">\relax</span><span class="keyword">\else</span> <span class="params">#1</span><span class="keyword">\fi</span>&#125;      <span class="comment">%</span></span><br><span class="line"><span class="keyword">\newcommand</span>&#123;<span class="keyword">\markname</span>&#125;[1]&#123;<span class="keyword">\if@npu@output@blindreview</span><span class="comment">%       % 打星盲评标记</span></span><br><span class="line">        <span class="keyword">\@npu@replaceitwithstars</span><span class="keyword">\relax</span><span class="keyword">\else</span> <span class="params">#1</span><span class="keyword">\fi</span>&#125;          <span class="comment">%</span></span><br></pre></td></tr></table></figure><p>利用<code>\newcommand</code>自定义命令,现代latex也可以使用</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\NewDocumentCommand</span>&#123;<span class="keyword">\命</span>令名&#125;&#123;参数类型&#125;&#123;定义内容&#125;</span><br></pre></td></tr></table></figure><ul><li><strong><code>m</code></strong>：代表必选参数 (Mandatory)。</li><li><strong><code>O{默认值}</code></strong>：代表可选参数 (Optional)，且带有默认值。</li></ul><div class="table-container"><table><thead><tr><th><strong>组成部分</strong></th><th><strong>是否必选</strong></th><th><strong>作用描述</strong></th></tr></thead><tbody><tr><td><strong><code>{\命令名}</code></strong></td><td><strong>必选</strong></td><td>你要创建的新命令名称，必须以反斜杠 <code>\</code> 开头。</td></tr><tr><td><strong><code>[参数个数]</code></strong></td><td>可选</td><td>定义该命令接受多少个参数，取值范围是 <strong>1–9</strong>。如果不写，默认为 0（即无参数）。</td></tr><tr><td><strong><code>[默认值]</code></strong></td><td>可选</td><td>如果写了这一项，新命令的<strong>第一个参数</strong>就变成了“可选参数”。如果不填，则使用这个默认值。</td></tr><tr><td><strong><code>{具体定义}</code></strong></td><td><strong>必选</strong></td><td>命令实际执行的内容。使用 <strong><code>#1</code>, <code>#2</code> … <code>#9</code></strong> 来代表传入的参数。</td></tr></tbody></table></div><div class="table-container"><table><thead><tr><th><strong>命令</strong></th><th><strong>行为逻辑</strong></th><th><strong>适用场景</strong></th></tr></thead><tbody><tr><td><strong><code>\newcommand</code></strong></td><td>如果已存在，<strong>报错</strong></td><td>定义全新的、确定没有重名的命令。</td></tr><tr><td><strong><code>\renewcommand</code></strong></td><td>如果不存在，<strong>报错</strong></td><td><strong>修改</strong>现有的命令（比如修改西工大模板默认的标题格式）。</td></tr><tr><td><strong><code>\providecommand</code></strong></td><td>无论是否存在，<strong>都不报错</strong></td><td>“如果没有就定义，有就跳过”。常用于编写兼容性代码。</td></tr><tr><td><strong><code>\DeclareRobustCommand</code></strong></td><td>无论是否存在，直接覆盖</td><td>增强型定义，防止命令在页眉、目录等“移动参数”中失效。</td></tr></tbody></table></div><p>另外也有利用<code>\let</code>和<code>\renewcommand</code>重新定义声明,</p><figure class="highlight tex"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">\let</span><span class="keyword">\old@toc</span><span class="keyword">\tableofcontents</span>                                <span class="comment">% 目录</span></span><br><span class="line"><span class="keyword">\renewcommand</span>&#123;<span class="keyword">\tableofcontents</span>&#125;&#123;                            <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\sDefault</span><span class="keyword">\phantomsection</span>                                <span class="comment">%</span></span><br><span class="line">    <span class="comment">% \addcontentsline&#123;toc&#125;&#123;chapter&#125;&#123;\contentsname&#125;           %</span></span><br><span class="line">    <span class="keyword">\bookmark</span>[dest=<span class="keyword">\HyperLocalCurrentHref</span>, level=0]&#123;<span class="keyword">\contentsname</span>&#125;</span><br><span class="line">    <span class="keyword">\old@toc</span> <span class="keyword">\cleardoublepage</span>                               <span class="comment">%</span></span><br><span class="line">&#125;                                                           <span class="comment">%</span></span><br><span class="line"><span class="keyword">\let</span><span class="keyword">\old@lof</span><span class="keyword">\listoffigures</span>                                  <span class="comment">% 图目录</span></span><br><span class="line"><span class="keyword">\renewcommand</span>&#123;<span class="keyword">\listoffigures</span>&#125;&#123;                              <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\sDefault</span><span class="keyword">\phantomsection</span>                                <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\addcontentsline</span>&#123;toc&#125;&#123;chapter&#125;&#123;<span class="keyword">\listfigurename</span>&#125;         <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\old@lof</span> <span class="keyword">\cleardoublepage</span>                               <span class="comment">%</span></span><br><span class="line">&#125;                                                           <span class="comment">%</span></span><br><span class="line"><span class="keyword">\let</span><span class="keyword">\old@log</span><span class="keyword">\listoftables</span>                                   <span class="comment">% 表目录</span></span><br><span class="line"><span class="keyword">\renewcommand</span>&#123;<span class="keyword">\listoftables</span>&#125;&#123;                               <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\sDefault</span><span class="keyword">\phantomsection</span>                                <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\addcontentsline</span>&#123;toc&#125;&#123;chapter&#125;&#123;<span class="keyword">\listtablename</span>&#125;          <span class="comment">%</span></span><br><span class="line">    <span class="keyword">\old@log</span> <span class="keyword">\cleardoublepage</span>                               <span class="comment">%</span></span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://zhuanlan.zhihu.com/p/77537952">【LaTex】cls文件编写和使用入门 - 知乎</a></li><li><a href="https://docs.aspose.com/tex/zh/net/latex-templates/">LaTeX 模板 - 快速入门指南</a></li><li><a href="https://xi-han.top/note/docs/latexcls.html">LaTeX .cls 文件编写经验 | 学，行之，上也</a></li><li><a href="https://github.com/rockyzhz/latexdoc-chinese-translation/blob/main/clsguide-current-zh-cn/clsguide-zh-cn.pdf">latexdoc-chinese-translation/clsguide-current-zh-cn/clsguide-zh-cn.pdf at main · rockyzhz/latexdoc-chinese-translation</a></li><li><a href="https://github.com/rockyzhz/latexdoc-chinese-translation">rockyzhz/latexdoc-chinese-translation: LaTeX 文档中文翻译项目</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/21/cls%E6%96%87%E4%BB%B6%E7%BC%96%E5%86%99%E5%AE%9E%E6%88%98/</id>
    <link href="https://www.sekyoro.top/2026/01/21/cls%E6%96%87%E4%BB%B6%E7%BC%96%E5%86%99%E5%AE%9E%E6%88%98/"/>
    <published>2026-01-21T14:31:40.000Z</published>
    <summary>
      <![CDATA[<p>已经使用latex写论文一段时间了，但对于cls模板还是缺乏认识。这里尝试从基础命令开始写一个简单模板。<br>]]>
    </summary>
    <title>cls文件编写实战</title>
    <updated>2026-01-31T12:04:43.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>一些智力题涉及到分治，贪心以及递推优化等问题，这里参考网上的资料进行总结<br><span id="more"></span></p><h2 id="赛马问题"><a href="#赛马问题" class="headerlink" title="赛马问题"></a>赛马问题</h2><p>有25匹赛马，每次赛跑最多只能有5匹马同场竞技，且没有计时设备。请设计一个方法，找出这25匹马中跑得最快的 前3名，最少需要进行多少次比赛？</p><blockquote><p>题目要求在 25 匹赛马中找到最快的前三名，但每次最多只能让 5 匹马同时比赛，且没有计时设备（只能通过相对排名判断快慢）。我们需要设计一种最优策略，使比赛次数最少</p></blockquote><p>思路：每5匹马进行一次比赛可以得到这5匹中最快的结果，但是比赛5次得到的5匹马中，有可能其中只有1匹在前三名中。所以先进行初步竞争，得到5组的第一名，再进行比赛得到最快的一匹马S1。剩下二三名S2,S3的竞争对手可能来自S1同组的第二三名以及S2同组的第二名。刚好5匹马再进行一次比赛就能确定全局第二三名。</p><p>可以通过逐步筛选的方式减少需要比较的赛马数量，尽快找到前三名。关键点在于：</p><ol><li><strong>初步分组</strong>：将 25 匹马分成 5 组，每组 5 匹，进行 5 场比赛，记录各组的排名。</li><li><strong>组间竞争</strong>：让 5 组的第一名再进行一场比赛，找到整体最快的马，同时确认可能的前三名范围。</li><li><strong>进一步筛选</strong>：基于已有排名，缩小搜索范围，最终找出前三名 </li></ol><p><strong>详细解法</strong></p><ol><li><strong>第一阶段：分组比赛（5 场）</strong><ul><li>将 25 匹马随机分成 5 组，每组 5 匹。</li><li>对每组进行一场比赛，记下每组的相对排名（例如 A 组排名：A1 &gt; A2 &gt; A3 &gt; A4 &gt; A5）。</li><li>共进行 5 场比赛。</li></ul></li><li><strong>第二阶段：组内冠军赛（1 场）</strong><ul><li>让 5 组的第一名进行一场比赛，得到最快的赛马（记作 S1），以及相对排名（S1 &gt; S2 &gt; S3 &gt; S4 &gt; S5）。</li><li>这场比赛能确定 S1 是整体最快的。</li><li>共进行 1 场比赛，累计 6 场比赛。</li></ul></li><li><strong>第三阶段：筛选可能的前 3 名</strong><ul><li>已知 S1 是最快的，但 S2、S3 也可能进入前三。</li><li>S2、S3 的竞争对手来自：<ol><li>S2 和 S3 本身（上一场比赛的第二、第三名）。</li><li>S1 原组的第二、第三名（S1 原组的前 3 名很可能都是整体前三）。</li><li>S2 原组的第二名（如果 S2 是全局第二，那它的组内第二名可能是全局第三）。</li></ol></li><li>让 S2、S3，S1 组的第二、第三名，S2 组的第二名进行一场比赛（总共 5 匹马）。 这场比赛的前两名就是最终的第二、第三名。</li><li>进行 1 场比赛，累计 7 场比赛</li></ul></li></ol><p><strong>深入思考</strong></p><ol><li><strong>为什么不能更少？</strong><ul><li>每场比赛最多 5 匹马，<strong>无法一次性比较 25 匹，因此至少需要先分组筛选。</strong></li><li>不能用计时设备，必须通过相对排名逐步筛选。</li></ul></li><li><strong>如果要找前 5 名呢？</strong><ul><li>额外增加一场比赛，<strong>在前三名的基础上再筛选可能的两匹马，总共 8 场比赛</strong>。</li></ul></li><li><strong>如果马匹数量增加呢？</strong><ul><li>N 匹马，每次比赛 M 匹，可以采用分组 + 逐步筛选的方法，比赛次数约为 ( O(N/M + log M) )。</li></ul></li></ol><p>这类题也有许多变种，</p><ul><li><p>赛马问题的变种：如果可以使用计时设备，策略会如何变化？ 有了计时器，<strong>每一场比赛的结果都是绝对的</strong>。</p></li><li><p>赛马问题的推广：如果需要找到前 K 名，如何优化？ 寻找前 $K$ 名时，核心在于<strong>实时更新“可能进入前 K 名”的候选名单</strong>。</p></li><li><p>竞赛排名算法：如何用更少的比较次数找到前 K 名（如锦标赛排序）？</p><p>寻找前 $K$ 名时，核心在于<strong>实时更新“可能进入前 K 名”的候选名单</strong>。</p><p>对于较大的 $K$，可以使用树状竞争结构：</p><ul><li><strong>建立胜者树：</strong> 每一轮比赛决出胜者向上晋级。</li><li><strong>回溯寻找：</strong> 找第 1 名需要 $N-1$ 次比较（在赛马模型中是分组并行）。</li><li><strong>求下一名：</strong> 找第 2 名时，只需在“输给过第 1 名”的那些马中寻找。以此类推，每找下一个名次，只需在局部被击败的集合中进行有限次数的比赛。</li></ul></li></ul><p>这个问题考察的是贪心策略、<strong>逐步筛选、递推优化的思维方式</strong>，适用于面试中的逻辑推理与算法设计考察。</p><h2 id="砝码称轻重"><a href="#砝码称轻重" class="headerlink" title="砝码称轻重"></a>砝码称轻重</h2><p>有8个外观完全相同的球，其中有一个球的重量与其他球不同，但不知道是更重还是更轻。使用一架天平秤，如何在最少的称重次数内确定这个特殊的球？</p><blockquote><p>由于我们不知道特殊球是更重还是更轻，因此在每次称重时都要尽量获得最大的信息量，以减少后续的判断次数。关键在于合理分组，并通过天平的倾斜情况缩小范围。</p></blockquote><p>思路：每次天平称重获取的信息是哪一方更重，</p><p>使用三分法策略，每次称重都能最大化筛选范围：</p><ol><li>每次将球分为三组：左盘、右盘、未称重组</li><li>根据天平的倾斜情况，逐步缩小可能的范围</li><li>最终在有限轮数内确定出特殊球及其重量差异（更重或更轻）</li></ol><p>这个问题不仅考察逻辑推理，还涉及信息论中的“三进制”思想（天平有三种状态：左重、右重、平衡）。</p><p>第一步：将球分为 3 组</p><p>我们将球编号为 1, 2, 3, 4, 5, 6, 7, 8。</p><p><strong>第一次称重：左盘 {1, 2, 3} vs 右盘 {4, 5, 6}</strong></p><p>根据称重结果，分为两种主要情况：</p><p>情况 A：天平平衡（特殊球在 {7, 8} 中）</p><p>如果平衡，说明 1-6 号全是标准球。</p><ul><li><strong>第二次称重：左盘 {7} vs 右盘 {1} (标准球)</strong><ul><li><strong>平衡</strong>：说明特殊球是 <strong>8</strong>。<ul><li><strong>第三次称重</strong>：{8} vs {1}。如果 8 重，则 8 是重球；反之亦然。</li></ul></li><li><strong>不平衡</strong>：说明特殊球就是 <strong>7</strong>。<ul><li>通过这次称重的结果直接知道 7 是偏重还是偏轻（例如 7 端下沉，则 7 重）。</li><li><strong>第三次称重</strong>：虽然已经知道是 7，但为了补齐逻辑（或验证），可以象征性完成。</li></ul></li></ul></li></ul><p>情况 B：天平不平衡（特殊球在 {1-6} 中）</p><p>假设左盘 <strong>{1, 2, 3} 下沉</strong>，右盘 <strong>{4, 5, 6} 上浮</strong>。 这意味着：要么 {1, 2, 3} 中有一个<strong>重</strong>球，要么 {4, 5, 6} 中有一个<strong>轻</strong>球。</p><p><strong>第二次称重：左盘 {1, 4} vs 右盘 {2, 5}</strong> （这里使用了“位置交换法”：保留 1 和 2，交换 4 和 5，排除 3 和 6）</p><ol><li><strong>依然左盘下沉</strong>：<ul><li>说明原因没变。特殊球只能是留在原位的 <strong>1 (重)</strong> 或 <strong>5 (轻)</strong>。</li><li><strong>第三次称重</strong>：{1} vs {标准球}。平衡则 5 轻，不平衡则 1 重。</li></ul></li><li><strong>变为右盘下沉（反转）</strong>：<ul><li>说明原因互换了。特殊球只能是交换了位置的 <strong>4 (轻)</strong> 或 <strong>2 (重)</strong>。</li><li><strong>第三次称重</strong>：{2} vs {标准球}。平衡则 4 轻，不平衡则 2 重。</li></ul></li><li><p><strong>天平平衡</strong>：</p><ul><li>说明特殊球是被拿掉的 <strong>3</strong> 或 <strong>6</strong>。</li><li><strong>第三次称重</strong>：{3} vs {标准球}。平衡则 6 轻，不平衡则 3 重。</li></ul><p><strong>相关题目</strong></p></li></ol><ul><li>如果是 12 个球，其中 1 个特殊球，最少称重次数是多少？（答案是 3 次，使用三分法）</li><li>如果可以使用数字秤（可以直接称重），是否有更快的方法？   质数</li><li>如果有两个特殊球，如何优化称重策略？</li></ul><p>本质上是 <strong>信息熵最大化策略</strong> 的应用，广泛用于搜索优化、问题拆解和决策树构建，是一个经典的逻辑推理题。</p><h2 id="药瓶毒白鼠"><a href="#药瓶毒白鼠" class="headerlink" title="药瓶毒白鼠"></a>药瓶毒白鼠</h2><p>有1000瓶药水，其中仅有一瓶是有毒的。已知服用毒药后，小白鼠会在24小时内死亡。你有10只小白鼠，如何在24小时内确定哪一瓶是毒药？</p><blockquote><p>在 24 小时内，我们最多只能进行 <strong>一次实验</strong>，即每只小白鼠在实验结束后要么存活，要么死亡（两种状态）。因此，我们需要用 <strong>10 只小白鼠的死亡情况来编码并唯一确定毒药瓶号</strong>。</p><p>10 只小白鼠，每只可以有 <strong>2 种状态（生存/死亡）</strong>，那么 10 只小白鼠能表示的最大编号数量为：<br>[ 2^{10} = 1024 ] 这足以唯一确定 1000 瓶药水中的毒药</p></blockquote><p>思路：将药瓶编号转换为二进制，并利用小白鼠的生死情况表示毒药编号</p><p>步骤1：药瓶编号二进制表示</p><p>将 1000 瓶药水从 1 号到 1000 号进行编号，并将编号转换为 <strong>10 位二进制数</strong>（因为 (2^{10} = 1024) 足够表示 1000 个编号）。例如：</p><ul><li>1 号药瓶 → 0000000001</li><li>2 号药瓶 → 0000000010</li><li>1000 号药瓶 → 1111101000</li></ul><p>步骤2：喝药</p><ul><li>每个药瓶的二进制位对应一只小白鼠。</li><li>如果该位是 1，则给对应的小白鼠喂这一瓶药，否则不喂。</li><li><p>例如：</p><ul><li>1 号药瓶（0000000001）→ 只喂第 10 只小白鼠。</li><li>2 号药瓶（0000000010）→ 只喂第 9 只小白鼠。</li><li>1000 号药瓶（1111101000）→ 1-5、7 只小白鼠都会喝到这一瓶药。</li></ul></li><li><p>记录哪些小白鼠死亡，哪些存活，并按 <strong>二进制位顺序</strong> 组合成一个 10 位的二进制数。</p></li><li>这个二进制数就是毒药瓶的编号。例如：<ul><li>如果第 1、3、5 只小白鼠死亡（其余存活），那么死亡的二进制码是 <strong>1010000000</strong>，转换成十进制就是 <strong>640</strong>，说明 <strong>640 号瓶子是毒药</strong></li></ul></li></ul><p>整个实验 <strong>仅需 1 次</strong>（24 小时）。</p><ul><li><strong>本质</strong>：利用二进制编码，将 10 只小白鼠的生死状态转换为唯一的药瓶编号。</li><li><strong>最少实验次数</strong>：1 次（24 小时内确定唯一毒药瓶）。</li><li><strong>最大可测范围</strong>：如果有 <strong>N 只小白鼠</strong>，最多可检测 <strong>(2^N - 1)</strong> 瓶药水。</li><li><strong>扩展</strong>：如果允许 48 小时（即 2 轮实验），则可用 <strong>信息熵最大化策略</strong> 进一步优化可检测的瓶子数量。</li></ul><p><strong>相关题目</strong></p><ul><li>如果有 2000 瓶药水，仍然只有 10 只小白鼠，如何优化实验策略？</li><li>如果毒药不是立即生效，而是需要 2-3 天才能导致小白鼠死亡，该如何调整策略？</li><li>如果能多次实验，但仍要尽快找到毒药，如何设计最优实验方案？</li></ul><p>如果实验要求“一次解决”，且毒药发作有时间窗（例如喂食后 24 小时内必死），我们可以通过<strong>错开喂食时间</strong>来增加每只老鼠携带的“信息进制”。</p><p><strong>策略：多轮筛选</strong></p><p>如果你有充足的时间进行多轮实验，可以将 2000 瓶药水分步处理。</p><p><strong>步骤：</strong></p><ol><li><strong>分组：</strong> 将 2000 瓶分为两组，A 组 1024 瓶，B 组 976 瓶。</li><li><strong>第一轮测试：</strong> 对 A 组进行二进制编码测试（10 只老鼠各喝对应位为 1 的混合液）。<ul><li><strong>结果 A：</strong> 如果有老鼠死亡，根据死亡组合直接锁定 A 组中的毒药。</li><li><strong>结果 B：</strong> 如果老鼠全活，毒药必在 B 组。</li></ul></li><li><strong>第二轮测试（若必要）：</strong> 针对 B 组再次进行二进制编码测试。</li></ol><p><strong>策略：三进制编码</strong></p><p>假设你有两天时间观察：</p><ul><li><strong>第 1 天：</strong> 给老鼠喝 A 组混合液。</li><li><strong>第 2 天：</strong> 给老鼠喝 B 组混合液。</li><li><strong>第 3 天看结果：</strong><ul><li>老鼠在第 1 天喂食后死（即第 2 天死）：代表状态 <strong>1</strong>。</li><li>老鼠在第 2 天喂食后死（即第 3 天死）：代表状态 <strong>2</strong>。</li><li>老鼠一直没死：代表状态 <strong>0</strong>。</li></ul></li></ul><p><strong>数学容量：</strong> 此时每只老鼠有 3 种状态，10 只老鼠的容量变为：</p><p>这个问题体现了 <strong>信息论、位运算、搜索优化</strong> 等思想，是经典的面试智力题</p><h2 id="绳子两头烧问题"><a href="#绳子两头烧问题" class="headerlink" title="绳子两头烧问题"></a>绳子两头烧问题</h2><p>有两根 <strong>燃烧时间为 1 小时</strong> 的绳子，但燃烧速度不均匀（某些部分烧得快，某些部分烧得慢）。如何 <strong>精确测量 45 分钟</strong>？</p><p>问题分析：</p><ol><li>绳子的燃烧时间为 1 小时，但燃烧速度不均匀。</li><li>两头点燃可以加速燃烧，如果 <strong>一根绳子从两头同时点燃，则其燃烧完时间是 30 分钟</strong>，即 <strong>燃烧速度加倍</strong>。</li><li>核心思路：<ul><li>通过 <strong>两头点燃</strong> 控制燃烧速度。</li><li>利用第二根绳子开始燃烧的时间点 来确保测量精确的 45 分钟。</li></ul></li></ol><p><strong>解法</strong></p><p><strong>步骤 1：点燃第一根绳子的两头</strong></p><ul><li>点燃绳子 A 的两头，同时 点燃绳子 B 的一头。</li><li>由于 A 两头点燃，它将在 30 分钟内完全烧完（即使燃烧速度不均匀，整个过程始终不会超过 30 分钟）。</li></ul><p><strong>步骤 2：点燃第二根绳子的另一头</strong></p><ul><li>当 A 完全烧完的瞬间（30 分钟过去了），立即 点燃 B 的另一头。</li><li>此时，B 已经燃烧了一半的时间（剩 30 分钟），如果现在再点燃另一头，它将 在 15 分钟内完全燃烧完（因为两头点燃燃烧速度加倍）。</li></ul><p><strong>步骤 3：等待 B 完全烧完</strong></p><ul><li>经过 <strong>15 分钟</strong>，B 也完全燃尽。</li><li>此时，总共时间：<strong>30 + 15 = 45 分钟</strong></li></ul><ol><li>利用两头点燃的特性，将 1 小时缩短到 30 分钟，或 30 分钟缩短到 15 分钟。</li><li>先点燃 A 的两头，精确得到 30 分钟。</li><li>利用 A 结束的时刻，点燃 B 的另一头，精确得到 15 分钟。</li><li><strong>最终总计 45 分钟，成功完成测量！</strong></li></ol><p><strong>相关题目</strong></p><ul><li>如果需要测量 15 分钟，该如何做？<ul><li>只需 <strong>两头点燃一根绳子</strong>，等待烧完即可。</li></ul></li><li>如果有 3 根相同性质的绳子，如何测量 75 分钟？<ul><li>先测 1 小时，再测 15 分钟，组合得到 75 分钟。</li></ul></li><li>如果绳子长度不同，但仍然是不均匀燃烧，如何测量精确时间？<ul><li>需要 按比例分割并利用两头点燃的技巧 进行组合测量。</li></ul></li></ul><h2 id="犯人猜颜色"><a href="#犯人猜颜色" class="headerlink" title="犯人猜颜色"></a>犯人猜颜色</h2><p>有 <strong>100 名犯人</strong> 站成一列，每人头上戴着一个 <strong>红色或蓝色</strong> 的帽子，自己看不到自己的帽子颜色，但能看到前面所有人的帽子颜色。<br>每个犯人只能说 <strong>“红色” 或 “蓝色”</strong>，从最后面（第 100 个人）开始依次说出自己帽子的颜色。如果猜错，立即被处决。请设计一个策略，使得 <strong>尽可能多的人存活</strong>。</p><blockquote><p>最优策略：使用奇偶校验</p></blockquote><p><strong>目标</strong>：确保 <strong>99 人存活</strong>，仅第 100 个人（最后面的人）可能被牺牲。</p><p>具体方法：</p><ol><li><strong>第 100 个人（最后面的犯人）说出前面 99 顶帽子的“奇偶性”</strong><ul><li>设定一个约定：<strong>红色 = 1，蓝色 = 0</strong></li><li>第 100 个人计算前面 <strong>99 个人帽子的“红色帽子总数”是奇数还是偶数</strong>，然后：<ul><li>如果总数是 <strong>偶数</strong>，他说“蓝色”</li><li>如果总数是 <strong>奇数</strong>，他说“红色”</li></ul></li><li><strong>他自己可能会被牺牲</strong>，但这一信息给后面的 99 个人提供了参考。</li></ul></li><li><strong>第 99 个人开始，根据第 100 个人的提示推理自己的帽子颜色</strong><ul><li>第 99 个人 <strong>已经知道前面 98 个人的帽子颜色</strong>，并且听到了第 100 个人报出的奇偶性。</li><li>他计算出 <strong>如果不包含自己的帽子，前 98 个人的红色帽子数量是否符合第 100 个人报的奇偶性</strong>：<ul><li><strong>如果符合</strong>，说明自己的帽子是 <strong>蓝色</strong></li><li><strong>如果不符合</strong>，说明自己的帽子是 <strong>红色</strong></li></ul></li><li>他说出正确的颜色，确保存活。</li></ul></li><li><strong>第 98 个人依次类推</strong><ul><li>每个人在听到前一个人报出的帽子颜色后，计算是否还保持 <strong>红色帽子数的奇偶性</strong>，以此确定自己的颜色。</li><li>依次进行，直到第 1 个人。</li></ul></li></ol><ul><li><strong>第 100 个人有 50% 概率生还</strong>（因为他只能根据奇偶性猜测自己的帽子）。</li><li><strong>其余 99 个人全部生还</strong>（因为他们完全可以通过前人的信息推理出自己的帽子颜色）。</li><li><strong>总存活人数：99 人或 100 人（最少 99 人存活）</strong>。</li></ul><p><strong>方案总结</strong></p><ol><li><strong>核心思想</strong>：使用 <strong>奇偶校验</strong> 作为全局信息，让第 100 个人提供 <strong>公共信息</strong>，后续所有人都可以准确判断自己的帽子颜色。</li><li><strong>牺牲最少人数</strong>：最坏情况下 <strong>仅第 100 个人可能被处决</strong>，而其他 <strong>99 个人必定存活</strong>。</li><li><strong>适用于更大规模的犯人</strong>：即使是 <strong>N 个人</strong>，此策略仍然有效，最多牺牲 1 人</li></ol><h2 id="猴子搬香蕉"><a href="#猴子搬香蕉" class="headerlink" title="猴子搬香蕉"></a>猴子搬香蕉</h2><p>有一堆香蕉和一只猴子。猴子 <strong>每次最多能搬 50 个香蕉</strong>，但 <strong>每次搬运 50 个香蕉需要消耗 1 个香蕉作为能量</strong>。<br>请问猴子最远能把香蕉搬运到 <strong>多远的地方</strong>？</p><p>假设香蕉总数为 <strong>N</strong>，我们用 <strong>分段搬运</strong> 的方式，把香蕉一点一点地运远。</p><p><strong>第一阶段：在起点的搬运逻辑</strong></p><ol><li><strong>猴子能带走的最大量 = 50 个</strong>，但每次 <strong>来回</strong> 需要消耗 1 个香蕉。</li><li>为了保证香蕉能够移动，猴子 <strong>每次必须携带多于 1 个香蕉，否则会在搬运过程中消耗完毕</strong>。</li><li>由于来回都要消耗香蕉，我们要计算 <strong>如何在不同距离下分批次运输香蕉</strong>。</li></ol><p><strong>第二阶段：递推计算最大搬运距离</strong></p><ol><li><strong>第一步：确定香蕉减少到足够少，可以一次性搬运完的阶段</strong><ul><li>例如：当香蕉数量 ≤ 50 时，一次就可以运完，无需往返。</li><li>但在初始阶段，猴子必须进行 <strong>多次搬运</strong>，因此需要优化香蕉消耗策略。</li></ul></li><li><strong>第二步：计算每个阶段的损耗</strong><ul><li>假设在某个距离 <strong>X</strong> 处，猴子还有 <strong>M</strong> 个香蕉，它可以继续向前搬运。</li><li>由于 <strong>每次 50 个为单位搬运</strong>，我们需要分批次将香蕉运输到一个新的存放点，直到香蕉数量少到可以一次运完。</li></ul></li></ol><p>假设起点香蕉总数为 <strong>N</strong>。</p><ol><li><strong>确定能完全搬运香蕉的终点</strong>：<ul><li>起初，猴子每次最多携带 50 个，每次来回都消耗 1 个。</li><li>运输到更远的地方时，每次运输成本会变高。</li><li>计算出 <strong>能运送的最大距离</strong>，并确保香蕉没有提前消耗完毕。</li></ul></li><li><strong>使用数学公式推导递推公式</strong><ul><li>假设起点香蕉数为 <strong>N</strong>，运输到距离 <strong>D</strong> 时，还剩下 <strong>M</strong> 个香蕉。</li><li>由于来回运输消耗，香蕉剩余量 <strong>M</strong> 递减，直到达到 <strong>小于等于 50</strong> 的情况时，猴子就可以一次性搬完剩余香蕉，不再消耗额外的香蕉</li></ul></li></ol><p>假设最初有 <strong>N = 300 个香蕉</strong>，计算猴子能搬运的最大距离。</p><ol><li><strong>第 1 段（起点到第一个存储点）</strong><ul><li>每次最多搬 <strong>50 个</strong>，但往返 <strong>需要消耗 1 个</strong>，所以实际 <strong>每 49 个香蕉才能推进 1 次</strong>。</li><li>如果要把 <strong>300 个香蕉搬运前进</strong>，需要分批次：<ul><li><strong>300 / 49 ≈ 6.12</strong>，所以大约可以推进 <strong>6 步</strong>（假设每一步 1 米）。</li><li>搬运到距离 <strong>6 米</strong> 时，还剩下 <strong>300 - 6 × 6 ≈ 264</strong> 个香蕉。</li></ul></li></ul></li><li><strong>第 2 段（继续往前推进）</strong><ul><li>由于此时香蕉变少，每次搬运的损耗降低（来回次数减少）。</li><li>继续计算，最终到达某个距离时，香蕉减少到 50 个左右，可以直接搬运完毕。</li></ul></li><li><strong>最终搬运距离</strong><ul><li>通过逐步计算可以得出，在 <strong>大约 30-35 米</strong> 左右的地方，猴子还能剩下 <strong>50 个香蕉</strong>，可以一次性运完。</li></ul></li></ol><p><strong>最终结论</strong></p><ul><li><strong>猴子最远可以搬运的距离 ≈ 30 到 35 米</strong>（具体数值取决于初始香蕉数量 N）。</li><li><strong>优化策略</strong>：<ul><li>先分批次搬运，把 <strong>香蕉集中存放到更远的地方</strong>，再逐步递推计算最大可搬运距离。</li><li>确保每个阶段的搬运量尽可能 <strong>减少不必要的损耗</strong>。</li><li>采用 <strong>分段存放策略</strong>，保证香蕉不在中途消耗完毕。</li></ul></li></ul><p><strong>相关面试题</strong></p><ul><li><strong>如果香蕉数量变成 10000 个，该策略如何扩展？</strong></li><li><strong>如果猴子每次消耗 2 个香蕉（而不是 1 个），如何影响最远搬运距离？</strong></li><li><strong>如果允许猴子暂存香蕉（类似于中转站），如何优化搬运策略？</strong></li></ul><p>这道题考察了 <strong>贪心算法、递推关系、优化策略</strong>，是一道 <strong>经典的数学与逻辑推理题</strong></p><h2 id="高楼扔鸡蛋"><a href="#高楼扔鸡蛋" class="headerlink" title="高楼扔鸡蛋"></a>高楼扔鸡蛋</h2><p>有一栋 <strong>100 层</strong> 高的建筑，给你 <strong>2 个相同的鸡蛋</strong>。已知鸡蛋从某一特定楼层或更高的楼层扔下 <strong>会碎</strong>，低于此楼层扔下 <strong>不会碎</strong>。请 <strong>设计一个策略</strong>，确定这个 <strong>临界楼层</strong>，要求 <strong>最坏情况下的扔鸡蛋次数最少</strong>。</p><ol><li><strong>核心目标</strong>：在最坏情况下，尽可能 <strong>减少扔鸡蛋的次数</strong>，确定鸡蛋会碎的最小楼层。</li><li><strong>基本约束</strong>：<ul><li><strong>两个鸡蛋</strong>：意味着我们无法像二分查找一样随意尝试高楼层（因为如果第一个鸡蛋碎了，第二个鸡蛋只能逐层检查）。</li><li><strong>最坏情况</strong>：要求策略在 <strong>任何情况下</strong>，最大扔鸡蛋次数尽可能少。</li></ul></li><li><strong>临界点</strong>：<ul><li>如果鸡蛋在某层 <strong>碎了</strong>，那么它的 <strong>安全楼层</strong> 只能在这层以下。</li><li>如果鸡蛋 <strong>没有碎</strong>，那么它的 <strong>临界楼层</strong> 只能在这层以上。</li></ul></li></ol><p><strong>思路</strong></p><ol><li>由于 <strong>只有 2 个鸡蛋</strong>，我们不能一次跳跃太多层，否则第一个鸡蛋碎了，第二个鸡蛋需要 <strong>一层一层试</strong>，导致次数过多。</li><li><strong>目标</strong>：减少最坏情况下的扔蛋次数。</li><li><strong>梯度递减策略</strong>：<ul><li>第一次从 <strong>某一层 X 开始扔</strong>，如果碎了，直接逐层检查 1 到 X-1 层；</li><li>如果 <strong>没碎</strong>，下一次 <strong>减少 1 层的跳跃步长</strong>，即从 X + (X-1) 层扔，以此类推；</li><li>这样保证即使第一个鸡蛋碎了，剩下的楼层数也不会太多，可以快速检查。</li></ul></li></ol><p>用第一个鸡蛋按递减步长投掷，使得无论在哪一层碎掉， 剩余次数都足以用第二个鸡蛋线性查找。</p><p>设鸡蛋的扔法为：</p><ul><li>第一次扔 X 层</li><li>第二次扔 X + (X-1) 层</li><li>第三次扔 X + (X-1) + (X-2) 层</li><li>…</li><li>直到 <strong>累加的总和 ≥ 100</strong></li></ul><p>即： [ X + (X-1) + (X-2) + … + 1 \geq 100 ] 这是一个 <strong>等差数列求和</strong>： [ \frac{X(X+1)}{2} \geq 100 ] 解得： [ X \approx 14 ] 即 <strong>第一步应从 14 层开始</strong>，然后 <strong>下一次递增 13 层，再递增 12 层</strong>，直到找到临界点。</p><ol><li><strong>最少需要 14 次扔鸡蛋</strong>，即可 <strong>保证在最坏情况下找到临界楼层</strong>。</li><li><strong>核心策略</strong>：<ul><li><strong>第一颗鸡蛋用来筛选大致范围</strong>，采用递减步长策略。</li><li><strong>第二颗鸡蛋用于精准查找</strong>，在较小范围内逐层检查。</li></ul></li></ol><p><strong>相关问题</strong> </p><ul><li>如果鸡蛋数量增加到 3 个，该如何优化？</li><li>如果是 1000 层高楼，而鸡蛋仍然是 2 个，该策略如何调整？</li><li>如果允许鸡蛋被摔碎后还能继续使用，该问题如何简化？ </li></ul><h2 id="轮流拿石子"><a href="#轮流拿石子" class="headerlink" title="轮流拿石子"></a>轮流拿石子</h2><p>桌上有一堆石子，两位玩家轮流从中拿取石子，每次至少拿1颗，最多拿3颗。拿到最后一颗石子的人获胜。请问先手是否有必胜策略？如果有，策略是什么？</p><p>思路： 如果最后只剩1-3颗石子且轮到己方则胜，如果是4颗轮到己方则必败。总结规律，如果是4倍数，则必须对方先手，如果不是，则己方先手，将石子拿到只剩4的倍数。</p><p><strong>基本分析</strong></p><ol><li><strong>小规模情况分析</strong><ul><li><strong>1 颗石子</strong>：先手直接拿走，<strong>胜利</strong> ✅</li><li><strong>2 颗石子</strong>：先手拿走全部，<strong>胜利</strong> ✅</li><li><strong>3 颗石子</strong>：先手拿走全部，<strong>胜利</strong> ✅</li><li><strong>4 颗石子</strong>：无论先手拿 1、2 或 3 颗，后手总能拿到最后一颗，因此先手 <strong>必败</strong> ❌</li></ul></li><li><strong>更大规模</strong><ul><li><strong>5 颗石子</strong>：先手拿 1 颗，剩下 4 颗，后手进入 <strong>必败</strong> 状态，先手 <strong>必胜</strong> ✅</li><li><strong>6 颗石子</strong>：先手拿 2 颗，剩下 4 颗，后手进入 <strong>必败</strong> 状态，先手 <strong>必胜</strong> ✅</li><li><strong>7 颗石子</strong>：先手拿 3 颗，剩下 4 颗，后手进入 <strong>必败</strong> 状态，先手 <strong>必胜</strong> ✅</li><li><strong>8 颗石子</strong>：无论先手拿 1、2、3 颗，后手都可以让剩余的石子变成 4 颗（必败状态），所以 <strong>先手必败</strong> ❌</li></ul></li></ol><p>由此可以发现：</p><ul><li>如果石子数是 <strong>4 的倍数</strong>，则 <strong>先手必败</strong>。</li><li>如果石子数 <strong>不是 4 的倍数</strong>，先手总能通过拿取合适数量，使对手进入 <strong>4 的倍数状态（必败状态）</strong>，从而 <strong>保证自己获胜</strong>。</li></ul><p><strong>结论与策略</strong></p><ol><li><strong>必胜条件</strong>：如果石子总数 <strong>不是 4 的倍数</strong>，先手可以 <strong>控制局势</strong>，让后手进入 4 的倍数状态，从而获胜。</li><li><strong>必败条件</strong>：如果石子总数 <strong>是 4 的倍数</strong>，先手无论如何操作，都会让对手进入 <strong>非 4 的倍数状态（必胜）</strong>，所以必败。</li><li><strong>具体策略</strong>：<ul><li><strong>若石子数为 4 的倍数</strong>，无论怎么拿都会输，<strong>先手必败</strong>。</li><li><strong>若石子数不是 4 的倍数</strong>，先手应该：<ul><li>先拿走 <strong>使剩余石子数变成 4 的倍数</strong>。</li><li>例如，石子总数 <strong>N % 4 = X（1≤X≤3）</strong>，先手应该 <strong>拿 X 颗</strong>，让剩余的石子变成 4 的倍数，使后手进入必败状态。</li></ul></li></ul></li></ol><p><strong>相关问题</strong></p><ul><li>如果每次可以拿 1~4 颗石子，策略如何变化？</li><li>如果游戏变成拿到最后一颗石子的人输，策略如何变化？4n+1先手必输</li><li>如果增加第三名玩家，游戏策略会发生怎样的变化？</li></ul><h2 id="蚂蚁走树枝"><a href="#蚂蚁走树枝" class="headerlink" title="蚂蚁走树枝"></a>蚂蚁走树枝</h2><p>在一根长度为L的树枝上，有n只蚂蚁，初始时它们分别位于不同的位置，且每只蚂蚁只能向左或向右直线行走，速度相同。当两只蚂蚁相遇时，它们会同时掉头反向行走。请问，所有蚂蚁离开树枝的最短时间是多少？</p><p>思路：相遇时同时掉头反向行走，完全可以当作不影响。因此离开树枝的最短时间就是每只蚂蚁到达距离自己前进方向最近端所需的最长时间</p><p>最短时间 = <strong>所有蚂蚁到最近端点的最短距离的最大值</strong><br>[ T_{\min} = \max(\min(x_1, L - x_1), \min(x_2, L - x_2), …, \min(x_n, L - x_n)) ] 其中 ( x_i ) 表示第 ( i ) 只蚂蚁的初始位置。</p><p>假设树枝长度 ( L = 10 )，蚂蚁位置为 {2, 6, 7}：</p><ul><li>蚂蚁 A 在 2，最短时间是 ( \min(2, 10 - 2) = 2 )</li><li>蚂蚁 B 在 6，最短时间是 ( \min(6, 10 - 6) = 4 )</li><li>蚂蚁 C 在 7，最短时间是 ( \min(7, 10 - 7) = 3 )</li></ul><p>所以最短时间为 <strong>4 秒</strong>，最长时间为 <strong>8 秒</strong>（假设所有蚂蚁都向右走）。</p><p>蚂蚁相遇后掉头，在数学上等价于直接穿过彼此。因此，为了让所有蚂蚁尽快离开树枝，每只蚂蚁都应朝最近的端点前进，总时间等于这些最短距离中的最大值</p><p><strong>相关问题</strong></p><ul><li>若蚂蚁改为相遇时不掉头怎么办？ 完全不变</li><li>若蚂蚁的速度不同，最短时间和最长时间如何计算？</li></ul><h2 id="海盗分金币"><a href="#海盗分金币" class="headerlink" title="海盗分金币"></a>海盗分金币</h2><p>有 5 个海盗抢到了一批金币，他们决定按以下方式分配：<strong>最年长的海盗提出一个分配方案</strong>，所有海盗（包括他自己）投票表决。如果<strong>不少于半数的海盗同意</strong>，则方案通过；否则，<strong>最年长的海盗被扔进海里</strong>，剩下的海盗继续上述过程。已知每个海盗都是<strong>理性的</strong>，既要保命，又要追求利益最大化。请问，<strong>最年长的海盗应如何分配金币</strong>？</p><ul><li><strong>海盗的目标</strong>：<ol><li><strong>保命</strong>：如果方案被否决，他就会被扔进海里。</li><li><strong>利益最大化</strong>：在确保不被扔进海里的前提下，自己要尽可能多地拿到金币。</li></ol></li><li><strong>投票规则</strong>：<ul><li>方案通过的最低票数 = <strong>总人数的一半或以上</strong>。</li><li>当海盗人数是奇数时，<strong>需要 ≥ ⌊N/2⌋ + 1 票</strong> 才能通过。</li><li>当海盗人数是偶数时，<strong>需要 ≥ N/2 票</strong> 才能通过</li></ul></li></ul><p><strong>逆向推理法</strong>（从少数海盗的情况推导）：</p><ol><li>设定基础情况（少数海盗时的分配方案）。</li><li>逐步增加海盗人数，推导出当前局势下的最优方案</li></ol><p>思路：从1个海盗来说，P1抢完，从两个海盗来说，P1,P2，P1提出方案自己同意即可。因此P1一定分100%</p><p>P1,P2,P3情况下，P1需要拉拢P3，否则会被P2完全拿完，因此P1可以给P3金币1%，自己分99%</p><p>P1,P2,P3,P4情况下，类似的，P1需要拉拢P3，否则剩下的P2和P4会进行分配。所以P1给P3金币1%，自己拿99%. 类似的，五个人的情况，P1需要拉拢P3和P5，否则会被P2和P4分配。</p><p>总结规律，当人数大于等于3人时，第一个提议的人需要拉拢其他一些人</p><p><strong>结论</strong></p><p><strong>最年长的海盗（P5）应提出的分配方案</strong>：</p><ul><li><strong>P5 = 97%</strong></li><li><strong>P4 = 0%</strong></li><li><strong>P3 = 1%</strong></li><li><strong>P2 = 0%</strong></li><li><strong>P1 = 2%</strong></li></ul><p>这样，P3 和 P1 会支持 P5，方案获得 3 票通过，P5 <strong>成功活下来</strong> 且获得最多的金币。</p><p><strong>相关题目</strong></p><ol><li><strong>如果是 6 个海盗，分配方案如何变化？</strong><ul><li>需要至少 3 票支持，P6 需要贿赂 P2 和 P4 等最容易被收买的海盗。</li></ul></li><li><strong>如果投票通过规则改为 3 票（无论总人数多少），该策略如何调整？</strong><ul><li>需要调整贿赂策略，可能需要更少金币换取 3 票。</li></ul></li><li><strong>如果金币数量不固定，而是 X 枚，该如何计算最优分配？</strong><ul><li>可以建立递推公式，计算最佳分配方案。</li></ul></li></ol><p>这道题考察了 <strong>逆向推理、博弈论、策略优化</strong>，是<strong>经典的智力题</strong>。<br>关键点：</p><ul><li>从少数海盗推导，逐步递推优化方案。</li><li>最年长海盗必须拉拢足够多的支持者，通过巧妙分配确保自己不被扔进海里。</li><li>每个海盗都理性，会尽可能选择对自己最有利的策略。</li></ul><h2 id="三个火枪手决斗策略"><a href="#三个火枪手决斗策略" class="headerlink" title="三个火枪手决斗策略"></a>三个火枪手决斗策略</h2><p>有三个火枪手，他们的射击准确率分别为 <strong>A、B、C</strong>（A &lt; B &lt; C）。他们按照顺序轮流开枪，直到<strong>只剩下一个人存活</strong>。每个人都希望<strong>自己活到最后</strong>。请问，他们各自的<strong>最佳策略</strong>是什么</p><p><strong>分析问题</strong></p><ol><li><strong>射击规则</strong>：<ul><li>三人按照顺序轮流射击（A → B → C → A → …）。</li><li>一旦某人被击中，他立即出局，不再行动。</li><li>直到最后只剩下 1 个人，决斗结束。</li></ul></li><li><strong>目标</strong>：<ul><li>每个人都希望自己存活到最后，所以他们会选择<strong>最优的射击策略</strong>，而不仅仅是击中对手。</li></ul></li><li><strong>射击策略的影响</strong>：<ul><li>如果某个射手<strong>直接击杀最强对手</strong>，可能让另一个对手获胜。</li><li>如果某个射手<strong>故意偏离目标</strong>，可能影响局势，提升自己胜率。</li><li>低命中率的射手<strong>可能希望让强者互相攻击</strong>，增加生存概率。</li></ul></li></ol><p><strong>最佳策略分析</strong></p><p><strong>第一步：最差的射手 A 的策略</strong></p><ul><li><strong>A 的准确率最低</strong>（A &lt; B &lt; C），如果他直接射杀 B 或 C，可能让剩下的强者立即反击他。</li><li><strong>A 的最佳策略</strong>：<strong>故意射偏！</strong><ul><li>如果 A 开枪但不射中，B 和 C 可能会互相攻击，A 可能得利。</li></ul></li></ul><p><strong>第二步：B 的策略</strong></p><ul><li><strong>B 知道 C 是最强的射手</strong>，如果不射杀 C，那么 C 可能会立即消灭他。</li><li><strong>B 的最佳策略</strong>：<strong>射杀 C</strong>，因为如果 C 存活，他的胜算极低。</li></ul><p><strong>第三步：C 的策略</strong></p><ul><li><strong>C 是最强的射手</strong>，如果轮到他时 B 还活着，他应该击杀 B。</li><li><strong>如果 C 和 A 对决，C 会选择射杀 A</strong>，因为他不会让 A 活到下一轮。</li></ul><p><strong>局势推导</strong></p><p>假设 A、B、C 的命中率分别是 30%（A）、60%（B）、90%（C）。</p><ol><li><strong>A 的回合</strong>：<ul><li>如果 A 直接射杀 B 或 C，他会立即成为下一个目标。</li><li><strong>A 的最佳策略是射偏</strong>，让 B 和 C 互相攻击。</li></ul></li><li><strong>B 的回合</strong>：<ul><li>B 发现 C 是最强者，必须射杀 C，否则 C 轮到后就会击杀他。</li><li><strong>B 会射向 C</strong>，并有 60% 的概率成功。</li></ul></li><li><strong>C 的回合</strong>（如果存活）：<ul><li>如果 C 存活，他一定会射杀 B。</li><li>如果 B 已经被 A 或 C 杀死，那么 C 会射击 A。</li></ul></li></ol><p><strong>最终策略总结</strong></p><div class="table-container"><table><thead><tr><th>轮次</th><th>当前射手</th><th>目标选择</th><th>影响</th></tr></thead><tbody><tr><td>1</td><td>A</td><td><strong>故意射偏</strong></td><td>让 B 和 C 互相攻击</td></tr><tr><td>2</td><td>B</td><td><strong>射击 C</strong></td><td>60% 概率击杀 C，40% 失败</td></tr><tr><td>3</td><td>C</td><td><strong>如果 C 还活着，射杀 B</strong></td><td>C 如果成功存活，就射杀 A</td></tr></tbody></table></div><ul><li><strong>A 的最佳策略</strong>：<strong>第一枪故意射偏，让 B 和 C 互相攻击</strong>，然后对剩下的人伺机而动。</li><li><strong>B 的最佳策略</strong>：<strong>射杀 C</strong>，否则自己很难存活。</li><li><strong>C 的最佳策略</strong>：<strong>射杀 B</strong>，然后在 A 面前稳赢。</li></ul><p><strong>相关题目</strong></p><ol><li><strong>如果四个火枪手进行决斗，策略如何变化？</strong><ul><li>A 可能仍然选择射偏，等待其他人互相攻击。</li><li>B、C、D 需要选择合适的目标，避免让自己成为被集火的对象。</li></ul></li><li><strong>如果他们的命中率更接近，例如 50%、60%、70%，策略是否会不同？</strong><ul><li>A 仍然可能射偏，但 B 可能不一定会射击 C，而是考虑击杀 A，减少竞争者。</li></ul></li><li><strong>如果他们可以移动，如何影响策略？</strong><ul><li>他们可能会选择躲避攻击，并寻找合适的时机射击。</li></ul></li></ol><p><strong>总结</strong></p><p>这道题考察了<strong>博弈论、概率分析和策略选择</strong>。<br>关键点：</p><ul><li><strong>A 需要利用 B 和 C 的冲突，自己存活下来</strong>。</li><li><strong>B 必须射杀 C，否则自己几乎没有胜算</strong>。</li><li><strong>C 必须先射杀 B，然后再对 A 取胜</strong>。<br>最终，<strong>A 通过射偏提高生存率，B 和 C 互相攻击，从而增加胜率</strong>。</li></ul><h2 id="囚犯拿豆子"><a href="#囚犯拿豆子" class="headerlink" title="囚犯拿豆子"></a>囚犯拿豆子</h2><p>有一群囚犯需要从一个装满豆子的罐子中拿豆子。每个人<strong>每次至少拿 1 颗，最多拿 4 颗</strong>。<strong>拿到最后一颗豆子的人将被释放</strong>。请问，<strong>先手是否有必胜策略？如果有，策略是什么？</strong></p><p>思路：如果还剩1-4颗豆子，则轮到自己胜利，如果是5颗，轮到自己则不论怎样对方都会进入1-4颗豆子，则己方必输。如果还剩6-9颗，则让对方进入5颗的状态。</p><p>因此：</p><ol><li><strong>如果豆子总数 N 是 5 的倍数</strong>，先手无论如何都会输。</li><li><strong>如果 N 不是 5 的倍数</strong>，先手应该拿走 1 ~ 4 颗，使得剩下的豆子变成 5 的倍数。</li></ol><p>总结规律，如果5的倍数则己方必输，否则将豆子拿到5的倍数让对方拿则己方必胜。</p><ul><li><strong>核心规律</strong>：<strong>5 的倍数是必败局面</strong>，否则先手有必胜策略。</li><li><strong>必胜策略</strong>：如果 N 不是 5 的倍数，先手应该让剩下的豆子变成 5 的倍数，这样对手无论如何都会输。</li><li>这道题本质上是<strong>尼姆博弈（Nim Game）</strong>的一种特殊形式，通过找出必败状态<strong>，反向推导</strong>必胜策略</li></ul><h2 id="学生猜生日"><a href="#学生猜生日" class="headerlink" title="学生猜生日"></a>学生猜生日</h2><p><strong>描述</strong>：有一个学生的生日在以下 10 个日期之一：</p><ul><li><strong>5月</strong>：15日、16日、19日</li><li><strong>6月</strong>：17日、18日</li><li><strong>7月</strong>：14日、16日</li><li><strong>8月</strong>：14日、15日、17日</li></ul><p>该学生分别告诉了两位同学 <strong>A</strong> 和 <strong>B</strong> 他的生日的月份和日期，但没有告诉他们具体的日期。</p><ul><li>A 只知道 <strong>月份</strong>，B 只知道 <strong>日期</strong>。</li><li><strong>A 说</strong>：“我不知道他的生日，但我知道 B 也不知道。”</li><li><strong>B 说</strong>：“起初我确实不知道他的生日，但现在我知道了。”</li><li><strong>A 说</strong>：“哦，那我也知道了。”</li></ul><p><strong>请问：这个学生的生日是哪一天？</strong></p><blockquote><p>经典 Cheryl 的生日问题</p></blockquote><p>思路：因为A确定B不知道，所以A的月份包含的日期应该不是唯一的，例如排除5月，6月。</p><p>所以B知道了A是7月或者8月，由于B起初不知道他生日，但知道月份只有可能是7、8月，则可能是7月16，8月15以及8月17日。由于A的月份是7或者8月，但其现在根据可能的日期15，16，17日确定了结果，则只有可能是7月，因此答案是7月16日。因为8月的话不能确定具体日期。</p><p><strong>相关题</strong></p><ul><li>如果日期不是 10 个，而是 20 个，如何推理？</li><li>如果 A 知道的是年份，B 知道的是月份，如何改编问题？</li><li>如何用程序实现该逻辑推理？</li></ul><p>这道题考察了 <strong>逻辑推理能力、排除法、信息传递分析</strong>，是一道 <strong>经典的信息推理题</strong>。</p><h2 id="水桶测量问题"><a href="#水桶测量问题" class="headerlink" title="水桶测量问题"></a>水桶测量问题</h2><p>你有一个 <strong>5 升</strong> 和一个 <strong>6 升</strong> 的水桶，如何精确地量出 <strong>3 升</strong> 的水？</p><blockquote><p><strong>贝祖定理</strong>：d=gcd(a,b) = a x + b y</p><p><strong>互质情况（gcd=1）</strong>：</p><ul><li>可以通过整数倍加减得到任意整数</li></ul><p><strong>非互质情况（gcd=d&gt;1）</strong>：</p><ul><li>只能得到 d 的倍数</li></ul></blockquote><ul><li><strong>基本规则</strong>：<ol><li>只能使用 <strong>两个水桶</strong>（5L 和 6L）。</li><li>只能 <strong>装满、倒空、互相倒水</strong>。</li><li>目标是 <strong>精确量出 3 升的水</strong>。</li></ol></li><li><strong>核心思路</strong>：<ol><li>这类问题属于 <strong>贝祖定理（Bézout’s Identity）</strong> 应用：如果两个数 <strong>a 和 b</strong> 互质（即最大公约数 GCD(a, b) = 1），那么可以通过整数加减得到从 <strong>1 到 GCD(a, b) 的倍数</strong> 的任意值。</li><li>6 和 5 的 <strong>GCD = 1</strong>，所以可以量出任意 1-5 升的水（因为 6 - 5 = 1）。</li><li>需要通过不断倒水，让其中一个桶最终留下 3 升。</li></ol></li></ul><p>如果有两个水桶，容量分别是 <strong>aL 和 bL</strong>，且它们互质，那么一定能量出从 <strong>1L 到 min(a, b) 的任意体积</strong></p><p><strong>更大的容量如何处理？</strong></p><ul><li>如果是 <strong>8L 和 12L</strong>，由于 <strong>GCD(8,12) = 4</strong>，那么只能量出 <strong>4L 的倍数</strong>（4L, 8L, 12L）。</li><li>只有当 <strong>水桶的容量互质</strong> 时，才能测量出任意 1L 到小桶容量的水量</li></ul><p><strong>相关题目</strong></p><ul><li><strong>如果水桶的容量是 4L 和 6L，能否量出 3L？</strong>（答案：不能，因为 GCD(4,6) = 2，无法得到 3L。）</li><li><strong>如何用程序实现找出所有可测量的水量？</strong>（可用 BFS/DFS 模拟水的倒入倒出状态。）</li><li><strong>如果允许有一个无限大的水源，如何优化步骤？</strong>（可以直接装满最大桶减少步骤。）</li></ul><p>这道题考察了 <strong>逻辑推理、数学定理（GCD/贝祖定理）、模拟思维</strong>，是一道 <strong>经典的智力题</strong>！</p><h2 id="沙漏计时问题"><a href="#沙漏计时问题" class="headerlink" title="沙漏计时问题"></a>沙漏计时问题</h2><p>有一个 <strong>6 分钟</strong> 和一个 <strong>8 分钟</strong> 的沙漏，如何利用它们精准计时 <strong>10 分钟</strong></p><ul><li><strong>沙漏的基本操作</strong>：<ol><li><strong>翻转</strong>：沙漏可以随时翻转，使沙子重新流动。</li><li><strong>暂停</strong>：可以在特定时间点观察沙漏状态，来控制计时。</li><li><strong>同步使用</strong>：可以同时启动两个沙漏，观察它们的相对状态来确定时间点。</li></ol></li><li><strong>目标</strong>：精准计时 <strong>10 分钟</strong>，需要找到合理的翻转时机</li></ul><p><strong>分析问题</strong></p><ul><li><strong>沙漏的基本操作</strong>：<ol><li><strong>翻转</strong>：沙漏可以随时翻转，使沙子重新流动。</li><li><strong>暂停</strong>：可以在特定时间点观察沙漏状态，来控制计时。</li><li><strong>同步使用</strong>：可以同时启动两个沙漏，观察它们的相对状态来确定时间点。</li></ol></li><li><strong>目标</strong>：精准计时 <strong>10 分钟</strong>，需要找到合理的翻转时机。</li></ul><p><strong>解法</strong></p><p><strong>步骤 1：同时启动两个沙漏</strong></p><ul><li><strong>开始时</strong>，同时翻转 <strong>6 分钟沙漏</strong> 和 <strong>8 分钟沙漏</strong>。</li><li>当 <strong>6 分钟沙漏流完</strong> 时，说明已经过去了 <strong>6 分钟</strong>，此时 <strong>8 分钟沙漏还剩 2 分钟的沙子</strong>。</li></ul><p><strong>步骤 2：翻转 6 分钟沙漏</strong></p><ul><li><strong>6 分钟沙漏用完的瞬间（6 分钟时刻）</strong>，立即翻转它，重新开始计时。</li><li><strong>同时等待 8 分钟沙漏流完</strong>（此时总共已经过去 <strong>8 分钟</strong>）。</li></ul><p><strong>步骤 3：8 分钟后，6 分钟沙漏还剩 4 分钟的沙子</strong></p><ul><li><strong>8 分钟到达时</strong>，6 分钟沙漏刚翻转过 <strong>2 分钟</strong>，因此它还剩 <strong>4 分钟的沙子</strong>。</li><li>这 <strong>4 分钟的沙子</strong> 继续流完，<strong>总共经过 10 分钟</strong>。</li></ul><p><strong>推广思考</strong></p><ol><li><strong>如何计时 4 分钟？</strong><ul><li>先让 <strong>6 分钟沙漏流完</strong>，然后翻转它，此时沙漏里有 <strong>2 分钟的沙子</strong>。</li><li>当 <strong>8 分钟沙漏流完时（总共 8 分钟）</strong>，6 分钟沙漏还剩 <strong>4 分钟的沙子</strong>，让它继续流完即可。</li></ul></li><li><strong>如果有 7 分钟和 11 分钟的沙漏，如何计时 10 分钟？</strong><ul><li>先让 <strong>7 分钟沙漏流完</strong>，同时 <strong>11 分钟沙漏还剩 4 分钟</strong>。</li><li>翻转 <strong>7 分钟沙漏</strong>，再等 <strong>4 分钟</strong>，共计 <strong>10 分钟</strong>。</li></ul></li><li><strong>如何用程序模拟沙漏计时？</strong><ul><li>可以用 <strong>两个计时变量</strong> 模拟流沙状态，并在特定时间点执行翻转操作。</li><li>使用 <strong>BFS/DFS 搜索状态变化</strong>，找出满足目标时间的最优策略。</li></ul></li></ol><p><strong>相关题</strong></p><ul><li>如果沙漏分别是 9 分钟和 12 分钟，如何计时 15 分钟？</li><li>如何用两个沙漏找出它们的最小公倍数（LCM）？</li><li>如何用沙漏计时非整分钟（如 7.5 分钟）？</li></ul><p>这道题考察了 <strong>时间管理、数学推理、状态转换思维</strong>，是一道 <strong>经典的逻辑推理面试题</strong>！</p><h2 id="电梯钻石问题"><a href="#电梯钻石问题" class="headerlink" title="电梯钻石问题"></a>电梯钻石问题</h2><p>你乘坐电梯从 <strong>1 楼</strong> 到 <strong>n 楼</strong>，每层电梯门都会打开，你可以看到该层的 <strong>钻石大小</strong>。你<strong>只能拿一次钻石</strong>，拿完后不能更换。请问如何才能拿到<strong>最大的一颗钻石</strong>？</p><blockquote><p>描述：你乘坐电梯从 <strong>1 楼</strong> 到 <strong>n 楼</strong>，每层电梯门都会打开，你可以看到该层的 <strong>钻石大小</strong>。你<strong>只能拿一次钻石</strong>，拿完后不能更换。请问如何才能拿到<strong>最大的一颗钻石</strong>？</p></blockquote><ul><li>你只能<strong>一次性决定是否拿钻石</strong>，一旦错过就不能回头。</li><li>你不知道后面的楼层会有什么样的钻石，所以要<strong>在合适的时机做决策</strong>。</li><li>这类似于经典的 <strong>“秘书问题”（The Secretary Problem）</strong>，也称为<strong>最佳停留问题（Optimal Stopping Problem）</strong>。</li></ul><script type="math/tex; mode=display">P(\text{最大钻石在第 k 层被拿到}) = \frac{1}{n} \cdot \frac{r}{k-1}\\ P(n,r) = \sum_{k=r+1}^{n} \frac{1}{n} \cdot \frac{r}{k-1}\\P(n,r) \approx \int_{r/n}^{1} \frac{r}{n x} dx = \frac{r}{n} \int_{r/n}^{1} \frac{1}{x} dx = \frac{r}{n} (\ln 1 - \ln(r/n)) = \frac{r}{n} \ln \frac{n}{r}\\f'(x) = \ln\frac{1}{x} - 1 = 0 \implies \ln\frac{1}{x} = 1 \implies x = \frac{1}{e} \approx 0.3679</script><p>1/n → 最大钻石出现在第 k 层</p><p>r/(k-1) → 在前 k-1 层，最大值出现在前 r 层的概率</p><p><strong>解法</strong></p><p>这个问题的最优策略是<strong>“37% 规则”（1/e 规则）</strong>，具体方法如下：</p><p><strong>第一阶段（观察期）</strong></p><ul><li><strong>前 37% 的楼层</strong>（即 <code>m ≈ 0.37 * n</code>，向下取整）<strong>不拿钻石</strong>，只是<strong>记录当前最大值</strong>。</li><li>这个阶段的目的是<strong>建立一个参考标准</strong>，大致了解钻石的大小分布情况。</li></ul><p><strong>第二阶段（决策期）</strong></p><ul><li>从 <code>m+1</code> 层开始，只要发现一个<strong>比观察期最大值还大的钻石</strong>，就立即拿下。</li><li>如果到了最后一层仍未发现更大的，只能拿最后一颗钻石（虽然可能不是最优，但至少保证有钻石）。</li></ul><p><strong>数学推导</strong></p><ul><li>采用 <strong>37% 规则</strong> 的策略，能保证在大多数情况下拿到接近最大的一颗钻石。</li><li><strong>成功率大约为 37%</strong>，而随机拿一颗钻石的成功率仅为 <code>1/n</code>，因此该策略能显著提高拿到最大钻石的概率。</li></ul><ol><li><strong>如果 <code>n</code> 变得很大，比如 1000 层，该策略是否依然适用？</strong><ul><li>是的，仍然可以用 37% 规则，只是 <code>m ≈ 370</code> 层观察，接下来寻找比观察期最大值更大的钻石。</li></ul></li><li><strong>如果你可以额外获得一次重新选择的机会，策略如何变化？</strong><ul><li><strong>第一轮使用 37% 规则</strong>，如果没拿到特别大的，可以在 <strong>剩余楼层中重新应用 37% 规则</strong>，提高成功率。</li></ul></li><li><strong>如果钻石的分布是已知的（例如，大多数大钻石在上层），如何优化策略？</strong><ul><li>可以调整观察期，比如在前 50% 楼层观察，以适应不同的分布情况</li></ul></li></ol><p><strong>相关题目</strong></p><ul><li><strong>“秘书问题”</strong>：如何在 <code>n</code> 名候选人中，选择最佳秘书？</li><li><strong>“停车问题”</strong>：如何在一条街道上，选择最佳停车位？</li><li><strong>“相亲问题”</strong>：如何在 <code>n</code> 次约会中，选择最合适的伴侣</li></ul><h2 id="金条切割问题"><a href="#金条切割问题" class="headerlink" title="金条切割问题"></a>金条切割问题</h2><p>有一根金条，需要支付给工人 <strong>7 天的工资</strong>，每天支付 <strong>金条的 1/7</strong>。你只能<strong>切割两次</strong>，请问如何完成支付？</p><p>思路：通过两次切割方式，使得切割得到的结果可以组合得到1，2，3，4，5，6值</p><p>可以<strong>采用贪心策略</strong>，让工人每次归还部分金条，使得支付刚好满足每天的要求</p><p><strong>切割方案</strong></p><ol><li><strong>第一次切割</strong>：把金条切出 <strong>1/7</strong>。</li><li><p><strong>第二次切割</strong>：把剩余部分切出 <strong>2/7</strong>，此时金条分成了 1/7、2/7 和 4/7 三段。</p></li><li><p><strong>切割出 1/7 和 2/7</strong>，剩下的部分自然就是 4/7。</p></li><li><strong>让工人归还部分金条</strong>，这样可以灵活组合每天的支付。</li><li><strong>保证每天都能精确支付 1/7</strong>，最终用完金条。</li></ol><p><strong>相关题目</strong></p><ul><li><strong>如果是 15 天工资，最少需要几次切割？</strong></li><li>1 2 3 4 5  6  7 8 9 10 11 12 13 14<ul><li>答案是 <strong>3 次</strong>，可以切出 1/15、2/15、4/15 和 8/15。</li></ul></li><li><strong>如果工人不能归还金条，该如何操作？</strong><ul><li>只能切割 <strong>6 次</strong>，每天给一块 1/7，无法优化支付方式。</li></ul></li></ul><h1 id="HR问题"><a href="#HR问题" class="headerlink" title="HR问题"></a>HR问题</h1><blockquote><p>谁说HR问题不算智力题?</p></blockquote><p>自我介绍（必问）</p><p><strong>HR 想听：你是谁 + 技术栈 + 项目亮点 + 稳定性</strong></p><p>模板直接用：</p><ul><li>我叫 XX，X 年 Java 后端开发经验</li><li>主要技术栈：SpringBoot、SpringCloud、MySQL、Redis、MQ、Elasticsearch 等</li><li>做过 XX 系统（高并发 / 分布式 / 微服务）</li><li>擅长：接口优化、问题排查、性能调优</li><li>性格：踏实、责任心强、抗压能力好</li></ul><h1 id="未来-3-年职业规划？"><a href="#未来-3-年职业规划？" class="headerlink" title="未来 3 年职业规划？"></a>未来 3 年职业规划？</h1><p><strong>HR 想听：稳定、技术深耕、不躺平、不浮躁</strong></p><p>标准答案：</p><ol><li>深耕<strong>后端技术</strong>，成为技术骨干</li><li>能独立负责复杂系统、做架构设计、性能优化</li><li>能带小需求 / 小模块，提升解决复杂问题的能力</li><li>长期技术路线，不轻易跳槽</li></ol><h1 id="你最大优点-缺点？"><a href="#你最大优点-缺点？" class="headerlink" title="你最大优点 / 缺点？"></a>你最大优点 / 缺点？</h1><p><strong>优点（后端匹配）：</strong></p><ul><li>逻辑思维强</li><li>喜欢钻研问题、排查 BUG、调优</li><li>责任心强、交付稳定</li><li>抗压能力好</li></ul><p><strong>缺点（安全、不致命）：</strong></p><ul><li>有时追求细节，会多花时间打磨</li><li>公共表达还需要提升</li><li>多任务并行时需要更合理规划时间</li></ul><h1 id="遇到最大压力-困难是什么？怎么解决？"><a href="#遇到最大压力-困难是什么？怎么解决？" class="headerlink" title="遇到最大压力 / 困难是什么？怎么解决？"></a>遇到最大压力 / 困难是什么？怎么解决？</h1><p><strong>HR 考察：抗压、解决问题、责任心</strong></p><p>后端万能故事：</p><ul><li>线上紧急 BUG / 接口超时 / 高并发问题</li><li>排查到凌晨 / 定位根因</li><li>解决、优化、复盘、避免再犯</li></ul><p><strong>结构：</strong></p><p>遇到问题 → 冷静分析 → 排查 → 解决 → 总结</p><h1 id="期望薪资？"><a href="#期望薪资？" class="headerlink" title="期望薪资？"></a>期望薪资？</h1><p><strong>安全回答：</strong></p><ul><li>期望在 <strong>XX–XX</strong> 区间</li><li>具体可以根据公司薪酬结构、绩效、福利综合沟通</li><li>希望能和个人能力、市场行情匹配</li></ul><p><strong>技巧：不说死，给区间。</strong></p><h1 id="还有什么想问我的？"><a href="#还有什么想问我的？" class="headerlink" title="还有什么想问我的？"></a>还有什么想问我的？</h1><p><strong>一定要问，显得你认真、稳定：</strong></p><ol><li>团队目前多少人？后端技术栈？</li><li>业务是 ToC 还是 ToB？有没有高并发场景？</li><li>试用期多久？是否带薪？</li><li>晋升机制、培训机制？</li><li>大概多久出结果？</li></ol><h1 id="为什么选择我们公司？"><a href="#为什么选择我们公司？" class="headerlink" title="为什么选择我们公司？"></a>为什么选择我们公司？</h1><p><strong>HR 想听：你是认真投的，不是海投</strong></p><p>万能回答：</p><ol><li>贵公司业务规模大，<strong>高并发、高流量</strong>场景多，技术挑战强</li><li>技术栈匹配（Java / 微服务 / 云原生）</li><li>团队技术氛围好，发展空间大</li><li>希望能在这边长期稳定发展</li></ol><h1 id="讲一个你最难忘-印象最深的项目经历"><a href="#讲一个你最难忘-印象最深的项目经历" class="headerlink" title="讲一个你最难忘 / 印象最深的项目经历"></a>讲一个你最难忘 / 印象最深的项目经历</h1><p><strong>HR 想听：你做了什么 → 遇到什么困难 → 怎么解决 → 收获</strong></p><p><strong>通用万能模板（后端必用）：</strong></p><blockquote><p>印象最深的是我做过的一个<strong>高并发接口优化 / 订单系统 / 缓存架构</strong>项目。</p><p>当时接口响应慢、数据库压力大，高峰期有超时风险。</p><p>我负责<strong>梳理瓶颈、加缓存、分库分表 / 异步化、优化 SQL</strong>，</p><p>最后把接口响应时间从几百毫秒降到几十毫秒，支撑了业务高峰期流量。</p><p>这次让我真正理解了<strong>后端性能优化、线上问题排查、高并发架构</strong>的重要性，也锻炼了我解决复杂问题的能力。</p></blockquote><p><strong>关键词：高并发、性能优化、线上问题、责任心、抗压。</strong></p><hr><h1 id="实习中学到了哪些可复用的能力和经验"><a href="#实习中学到了哪些可复用的能力和经验" class="headerlink" title="实习中学到了哪些可复用的能力和经验"></a>实习中学到了哪些可复用的能力和经验</h1><p><strong>HR 考察：你会不会总结、能不能快速上手</strong></p><p>直接背这套：</p><blockquote><ol><li>工程化开发规范</li></ol><p>   学会了写可维护、可扩展的代码，重视注释、日志、异常处理。</p><ol><li>问题排查能力</li></ol><p>   学会看日志、定位 BUG、分析接口耗时、排查线上问题。</p><ol><li>协作沟通能力</li></ol><p>   跟测试、产品、前端对接，理解需求、保证交付。</p><ol><li>责任心与交付意识</li></ol><p>   互联网节奏快，必须按时交付、对自己代码负责。</p><ol><li>学习能力</li></ol><p>   快速熟悉业务、技术栈，能独立承担模块开发。</p></blockquote><p><strong>一句话总结版：</strong></p><blockquote><p>实习让我具备了<strong>规范开发、问题排查、团队协作、独立负责需求</strong>的能力，这些到任何公司都能复用。</p></blockquote><hr><h1 id="平时如何使用-AI"><a href="#平时如何使用-AI" class="headerlink" title="平时如何使用 AI"></a>平时如何使用 AI</h1><p><strong>HR 想听：你会用 AI 提高效率，不依赖、不偷懒</strong></p><p>标准回答：</p><blockquote><p>我主要用 AI 来<strong>提高开发效率</strong>：</p><ul><li><p>写代码时辅助生成模板、工具类、SQL；</p></li><li><p>排查问题时帮我分析报错、定位思路；</p></li><li><p>学习新技术时快速梳理知识体系、画架构图；</p></li><li><p>写文档、接口注释、梳理业务逻辑。</p></li></ul><p>  但我</p><p>  不会完全依赖</p><p>  ，关键逻辑、线上代码都会自己校验、理解、测试，保证质量。</p></blockquote><hr><h1 id="你平时如何学习新的知识-技术"><a href="#你平时如何学习新的知识-技术" class="headerlink" title="你平时如何学习新的知识 / 技术"></a>你平时如何学习新的知识 / 技术</h1><p><strong>后端标准答案，非常加分：</strong></p><blockquote><p>我学习一般分三步：</p><ol><li>先搭建知识体系</li></ol><p>   看官方文档、视频，先懂整体架构、核心概念。</p><ol><li>动手实践</li></ol><p>   写 Demo、搭环境、跑通流程，比如学 Redis、MQ、SpringCloud 都会自己实操。</p><ol><li>结合项目复盘</li></ol><p>   把新技术用到实际需求里，理解为什么用、解决什么问题。</p><p>   平时也会看技术文章、极客时间、GitHub，保持持续学习。</p></blockquote><hr><h1 id="你对xx公司怎么看"><a href="#你对xx公司怎么看" class="headerlink" title="你对xx公司怎么看"></a>你对xx公司怎么看</h1><blockquote><p>xx是<strong>非常靠谱、技术扎实、重服务、重体验</strong>的大厂。</p><p>技术体系很完善，在<strong>高并发、分布式、物流、供应链、云服务</strong>这些领域都有很强的技术积累。</p><p>而且xx的业务场景真实、有挑战，非常适合后端工程师成长。</p><p>我个人很认可xx的文化和技术氛围，希望能加入团队，长期稳定发展。</p></blockquote><p><strong>绝对不要：</strong></p><ul><li>只说 “大、有名、福利好”。</li></ul><hr><h1 id="能接受的工作时间-加班怎么看"><a href="#能接受的工作时间-加班怎么看" class="headerlink" title="能接受的工作时间 / 加班怎么看"></a>能接受的工作时间 / 加班怎么看</h1><p><strong>互联网标准回答：</strong></p><blockquote><p>我能接受<strong>互联网正常的工作节奏</strong>。</p><p>项目紧急、上线、高峰期，我完全可以配合加班，保证项目稳定上线。</p><p>但我也会<strong>提高工作效率</strong>，提前规划，尽量避免无效加班，保证交付质量和稳定性。</p></blockquote><p><strong>HR 要的是：态度好、能扛事、不矫情。</strong></p><hr><h1 id="学习-工作中有没有很忙的时候？怎么平衡？"><a href="#学习-工作中有没有很忙的时候？怎么平衡？" class="headerlink" title="学习 / 工作中有没有很忙的时候？怎么平衡？"></a>学习 / 工作中有没有很忙的时候？怎么平衡？</h1><p><strong>标准答案：</strong></p><blockquote><p>肯定有忙的时候，比如<strong>项目上线、需求密集、考试 / 毕设叠加</strong>的时候。</p><p>我一般会：</p><ol><li><p><strong>先排优先级</strong>，先做重要紧急的；</p></li><li><p><strong>拆分任务</strong>，一步步推进，不拖延；</p></li><li><p><strong>提高专注度</strong>，提高单位时间效率；</p></li><li><p>及时沟通，不硬扛，有风险提前同步。</p><p>这么多年学习和实习，我已经能很好平衡压力，保证稳定输出。</p></li></ol></blockquote><h1 id="还有什么想问我？（必问，加分）"><a href="#还有什么想问我？（必问，加分）" class="headerlink" title="还有什么想问我？（必问，加分）"></a>还有什么想问我？（必问，加分）</h1><p>你可以直接问这 4 个：</p><ol><li>咱们团队<strong>后端技术栈</strong>是什么？</li><li>业务主要是<strong>ToC 还是 ToB</strong>，有没有高并发场景？</li><li>新人入职会有<strong>导师带</strong>吗？</li><li>面试流程大概还有几轮？</li></ol><p>公司对<strong>技术新人的培养和晋升</strong>机制是怎样的？</p><p>团队内部有没有<strong>技术分享、复盘、培训</strong>？</p><p>这个岗位未来 1 年的<strong>发展方向</strong>是什么？</p><p>团队现在<strong>后端大概多少人</strong>？</p><p>这个岗位的<strong>核心考核指标</strong>是什么？</p><p>咱们团队目前<strong>后端技术栈</strong>主要是哪些？</p><p>这个岗位主要负责<strong>哪块业务</strong>？是 ToC 还是 ToB？</p><p>新人入职后会有<strong>导师带教</strong>吗？前期怎么融入？</p><h2 id="关键词描述自己"><a href="#关键词描述自己" class="headerlink" title="关键词描述自己"></a>关键词描述自己</h2><p><strong>严谨细致</strong>（写代码、排查问题需要）</p><p><strong>执行力强</strong>（互联网需求多，交付快）</p><p><strong>问题解决能力强</strong>（后端最核心）</p><p><strong>逻辑清晰</strong></p><p><strong>踏实靠谱</strong></p><p><strong>持续学习</strong></p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/16/%E6%99%BA%E5%8A%9B%E9%A2%98%E6%80%BB%E7%BB%93/</id>
    <link href="https://www.sekyoro.top/2026/01/16/%E6%99%BA%E5%8A%9B%E9%A2%98%E6%80%BB%E7%BB%93/"/>
    <published>2026-01-16T08:36:18.000Z</published>
    <summary>
      <![CDATA[<p>一些智力题涉及到分治，贪心以及递推优化等问题，这里参考网上的资料进行总结<br>]]>
    </summary>
    <title>智力题总结</title>
    <updated>2026-04-03T08:21:16.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sekyoro</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>目前LLM应用集中在开发所谓AI智能体,理想中的AI智能体能够自主感知环境并作出决策,与环境的变化不断反馈,同时具备记忆,反思和工具能力.而目前的AI应用中,效果最好,成本也低的还是RAG.<br><span id="more"></span></p><h2 id="RAG应用场景"><a href="#RAG应用场景" class="headerlink" title="RAG应用场景"></a>RAG应用场景</h2><p>RAG全称是“Retrieval-Augmented Generation”，即“检索增强的生成”。当设计一个LLM问答应用，模型<strong>需要处理用户的领域问题时</strong>，尽管大模型通常表现出色，但有时提供的答案并不准确，甚至可能出现错误。<strong>当用户需要获取实时信息时，模型无法及时提供最新的答案</strong>。这种现象在LLM应用中较为常见。</p><p>这些模型仍然存在一些无法忽视的局限性。其中，<strong>领域知识缺乏</strong>是最明显的问题。大模型的知识来源于训练数据，这些数据主要来自公开的互联网和开源数据集，无法覆盖特定领域或高度专业化的内部知识。</p><p><strong>信息过时</strong>则指模型难以处理实时信息，因为训练过程耗时且成本高昂，模型一旦训练完成，就难以获取和处理新信息。</p><p>此外，<strong>幻觉问题</strong>是另一个显著的局限，模型基于概率生成文本，有时会输出看似合理但实际错误的答案。最后，<strong>数据安全性</strong>在企业应用中尤为重要，如何在确保数据安全的前提下，使大模型有效利用私有数据进行推理和生成，是一个具有挑战性的问题。</p><p><img data-src="https://s2.loli.net/2026/01/13/8gjtfrQ3vART42E.png" alt="image-20260113140539536"></p><p>RAG系统的应用场景主要集中在专业领域和企业场景,</p><p>RAG的核心思想是<strong>利用外部知识库或数据集来辅助模型的生成过程</strong>。具体来说，RAG通常包含以下关键步骤。</p><ol><li>检索阶段：首先，模型会根据输入的查询或问题，从预先构建的索引中检索出最相关的数据、文档或文本片段。</li><li>生成阶段：随后，模型会使用这个综合的表示来生成答案或输出文本。在问答任务中，这通常意味着生成一个对原始查询的直接回答。</li></ol><p>选择RAG而不是直接将所有知识库数据交给大模型处理，主要是<strong>因为模型能够处理的token数有限，输入过多token会增加成本。更重要的是，提供少量相关的关键信息能够带来更优质的回答</strong>。</p><p><strong>RAG模型的核心思想在于通过检索与生成的有机结合，弥补大模型在处理领域问题和实时任务时的不足。</strong>传统的生成模型在面对复杂问题时，往往由于知识储备不足，生成出错误或无关的回答。而RAG通过检索模块获取相关的背景信息，使生成模块能够参考这些信息，从而生成更具可信度和准确性的答案。这种方法不仅增强了生成内容的准确性，还提高了模型在应对特定领域知识和动态信息时的适应能力。</p><p><img data-src="https://s2.loli.net/2026/01/13/bSG4or8AcmnNkxP.png" alt="image-20260113141707928" style="zoom:67%;" /></p><p>RAG标准流程由<strong>索引</strong>（Indexing）、<strong>检索</strong>（Retriever）和<strong>生成</strong>（Generation）三个核心阶段组成。</p><ol><li>索引阶段，通过<strong>处理多种来源多种格式的文档提取其中文本，将其切分为标准长度的文本块</strong>（chunk），并进行<strong>嵌入向量化（embedding），向量存储在向量数据库</strong>（vector database）中。</li><li>检索阶段，用户输入的查询（query）被转化为向量表示，通过相似度匹配从向量数据库中检索出最相关的文本块。</li><li>最后生成阶段，检索到的相关文本与原始查询共同构成提示词（Prompt），输入大语言模型（LLM），生成精确且具备上下文关联的回答。</li></ol><p><strong>索引是RAG系统的基础环节</strong>，包含四个关键步骤。</p><ol><li>首先，将各类数据源及其格式（如书籍、教材、领域数据、企业文档等，txt、markdown、doc、ppt、excel、pdf、html、json等格式）统一解析为纯文本格式。</li><li>接着，根据文本的语义或文档结构，将文档分割为小而语义完整的文本块（chunks），确保系统能够高效检索和利用这些块中包含的信息。</li><li>然后，使用文本嵌入模型（embedding model），将这些文本块向量化，生成高维稠密向量，转换为计算机可理解的语义表示。</li><li>最后，将这些向量存储在向量数据库(vector database)中，并构建索引，完成知识库的构建。这一流程成功将外部文档转化为可检索的向量，支撑后续的检索和生成环节。</li></ol><p><strong>检索是连接用户查询与知识库的核心环节。</strong>首先，用户输入的问题通过同样的文本嵌入模型转换为向量表示，将查询映射到与知识库内容相同的向量空间中。通过相似度度量方法，检索模块从向量数据库中筛选出与查询最相关的前K个文本块，这些文本块将作为生成阶段输入的一部分。通过相似性搜索，检索模块有效获取了与用户查询切实相关的外部知识，为生成阶段提供了精确且有意义的上下文支持。</p><p><strong>生成是RAG流程中的最终环节</strong>，将检索到的相关文本块与用户的原始查询整合为增强提示词（Prompt），并输入到大语言模型（LLM）中。LLM基于这些输入生成最终的回答，确保生成内容既符合用户的查询意图，又充分利用了检索到的上下文信息，使得回答更加准确和相关，充分使用到知识库中的知识。通过这一过程，RAG实现了具备领域知识和私有信息的精确内容生成。</p><h2 id="Fine-tuning-vs-RAG"><a href="#Fine-tuning-vs-RAG" class="headerlink" title="Fine-tuning vs RAG"></a>Fine-tuning vs RAG</h2><p>Fine-tuning 作为一种直接针对预训练语言模型进行额外训练的方法，能够让模型更好地适应特定领域或任务。这种方法的核心优势在于，<strong>能够显著提升模型在目标任务上的表现</strong>。通过使用领域特定的数据集，Fine-tuning 可以让模型学习到行业术语、专业知识结构和特定的表达方式。这使得Fine-tuned模型在处理专业领域的问题时，能够提供更加准确、相关的回答。例如，在极客时间，我们把作者的实战经验从文章中提取出来，然后基于开源模型进行微调，让 LLM 可以输出生产环境直接可用的技术方案，而不是一些通用解决方案。</p><p><strong>微调更适合需求稳定、领域知识固定且不需要频繁更新知识库的场景</strong>。通过使用特定领域的数据对模型进行深度优化，微调可以提升模型在特定任务或领域中的推理能力，确保输出内容的专业性和一致性。因此，当任务侧重于某一特定领域，并且对实时信息的依赖较低时，微调更能满足这些需求</p><p>此外，Fine-tuning 后的模型响应速度快，无需在运行时检索外部知识库，这使得它特别适合需要实时响应的场景，比如在线客户服务或实时决策支持系统。举个例子，在我们的用户对话功能中，有时候人工客服可能不在线，用户提问如果能快速得到响应，及时解答用户问题，可以大大提高用户的信任感。</p><p>当然，使用 Fine-tuning 也要付出一些额外的成本。它需要大量的标注数据和较高的计算资源，这对许多企业来说可能是一个门槛。而且，一旦模型经过 Fine-tuning，要更新其知识就需要重新训练，如果是经常变化的知识，可能成本就非常高了。</p><p>相比之下，RAG 技术提供了一种更为灵活的解决方案，可以更好地处理 “经常变化” 的知识。RAG 通过将大语言模型与外部知识库结合，在生成回答时实时检索相关信息。这种方法非常灵活，由于是自己设计的知识库，可以做各种优化操作，保证把最准确的知识呈现给用户。企业可以随时更新知识库中的信息。</p><p>RAG 的另一个重要优势是其<strong>可解释性</strong>。由于每个回答都可以追溯到具体的知识来源，这大大增强了AI系统的可信度和透明度。在需要严格监管或高度问责的行业，如金融服务或政府部门，这一特性尤为重要。然而，RAG也有其局限性。由于需要实时检索知识库，RAG系统的响应速度可能不如 Fine-tuned 模型快。此外，RAG的效果高度依赖于<strong>知识库的质量</strong>和<strong>检索算法的效率</strong>，这要求我们投入大量资源来维护和优化知识库。</p><p>在实际应用中，我们可以将 Fine-tuning 和 RAG 结合使用，以充分发挥两种方法的优势。这种混合方法可以应对更复杂的场景和挑战。例如，要做一个极客时间客服系统，我们可以使用 Fine-tuned 模型来处理常见的、结构化的查询，比如一些通识或者固定不变的知识，保证快速、准确的响应。同时，对于涉及最新产品信息或政策变更的问题，系统可以切换到 RAG 模式，利用实时更新的知识库来提供最新、最相关的信息。在产品研发过程中，可以使用企业内固定的技术相关文档进行 Fine-tuning，让 LLM 回答的内容都是符合企业开发技术范围，再配合上 RAG 把一些外部的技术文档作为补充知识库，两者结合，在内部就可以很好地辅助产研人员工作。</p><h3 id="未来方向"><a href="#未来方向" class="headerlink" title="未来方向"></a>未来方向</h3><p><strong>第一个方向是动态知识图谱的集成。</strong>传统的RAG系统通常使用静态的知识库，而动态知识图谱将为RAG带来更智能、更灵活的知识管理能力。这种系统不仅能够实时更新知识，还能自动建立知识之间的关联。</p><p>在前面课程中提到的 Mem0 记忆框架，最近更新了一个特性，就是 graph memory。它把用户的记忆拆分成记忆节点和关系，通过使用图数据库，保存下这些关系和节点，在使用记忆的时候，会通过图检索技术找到记忆之间的关联，从而找到关键的记忆点，然后结合当时的详细记忆，最终给出回答。这种技术可以让用户在海量记忆中去查询问题的答案，相比传统的 RAG，极大程度地提高了记忆的相关性和连续性。</p><p>还有就是最近大火的<a href="https://microsoft.github.io/graphrag/">GraphRAG</a>。GraphRAG 本身是一个理论，这个项目是由微软开源的，它包括从原始文本中提取知识图，构建社区层次结构，为这些社区生成摘要，然后在执行基于 RAG 的任务时利用这些结构等等功能。</p><p><strong>第二个方向是个性化</strong> <strong>RAG。</strong>未来的RAG系统将能够根据用户的背景、偏好和使用历史来动态调整其检索和生成策略。</p><p>例如，一个个性化的 RAG 系统可以根据用户的知识水平、学习风格和关注点来推荐内容和解答用户问题。这种个性化不仅限于内容的选择，还包括表达方式的调整，使得AI助手能够以最适合每个用户的方式进行交流，可以根据不同用户的喜好风格，调整 AI 小助手的说话方式。这将大大提高用户体验，使AI系统更加贴近人类的交互需求，带给用户更多的亲切感。</p><p><strong>第三个方向是多模态 RAG。</strong>上述的讨论都建立在文本模型上，如果再加上视觉模型，让大模型能像人眼一样观察世界，再把看到的东西内化到知识库中，这将会大大拓宽 RAG 的应用范围，让更多的业务能够用上大模型技术。</p><p>比如，在医疗领域，使用多模态 RAG 系统可能会同时分析病人的症状描述（文本）、X光片（图像）、心电图（时序数据）和医生的口头诊断（音频）。系统不仅能够理解每种模态的信息，还能够在这些不同模态之间建立联系，提供更加全面和准确的诊断建议。相对于只有文本内容，这种方式能够得出更加准确和有深度的诊断，也更加符合医生的诊断习惯。通过与多模态结合，将会让大模型在更多领域发挥价值，甚至会产生新的产品形态或者服务方式。企业在这个方向上进行探索，会带来创新性的收获。</p><h1 id="IM系统"><a href="#IM系统" class="headerlink" title="IM系统"></a>IM系统</h1><p>基于 Netty ⾃定义 WebSocket 协议实现⾼性能即时通讯服务；利⽤ Redisson 缓存会话列表与⽤⼾路由以优 化加载性能同时维护集群环境下的⽤⼾状态。此外，通过设计离线消息增量拉取逻辑，确保⽤⼾重连后消息有 序不丢失。</p><p>是基于 Netty 搭建高性能 WebSocket 即时通讯服务，解决三个核心问题：</p><p>①单节点高性能通讯 ②集群下用户状态同步 ③用户重连后离线消息不丢失且加载高效。整体架构上，用 Netty 做底层通讯层，Redisson（Redis）做分布式缓存 / 消息广播层，再通过离线消息增量拉取的业务逻辑保证消息一致性。</p><h4 id="Netty-自定义-WebSocket-实现高性能即时通讯"><a href="#Netty-自定义-WebSocket-实现高性能即时通讯" class="headerlink" title="Netty + 自定义 WebSocket 实现高性能即时通讯"></a>Netty + 自定义 WebSocket 实现高性能即时通讯</h4><blockquote><p>Netty 是一个<strong>异步事件驱动</strong>的网络应用框架，用于快速开发高性能、高可靠性的网络服务器和客户端</p><p><strong>异步非阻塞</strong>：基于 NIO，支持高并发</p><p><strong>高性能</strong>：零拷贝、内存池、对象池</p><p><strong>高可靠性</strong>：完善的异常处理、断连重连</p><p><strong>高度可定制</strong>：灵活的线程模型、编解码</p></blockquote><p>首先是底层通讯层的设计，选择 Netty 是因为它的 NIO 非阻塞 Reactor 模型天生适配高并发的长连接场景，比传统 BIO 的 Tomcat WebSocket 性能提升显著：</p><ul><li>协议层处理：在 Netty 的 ChannelPipeline 中做了分层 Handler 设计：<ul><li>先通过<code>HttpServerCodec</code>+<code>HttpObjectAggregator</code>处理 HTTP 协议（因为 WebSocket 握手基于 HTTP），保证 HTTP 请求的完整性；</li><li>再通过<code>WebSocketServerProtocolHandler</code>将 HTTP 协议升级为 WebSocket，支持全双工长连接，同时配置帧大小、握手超时等参数避免异常连接；</li><li>核心的心跳保活：加入<code>IdleStateHandler</code>设置 60 秒读超时（用户端 60 秒没发心跳则判定连接失效），配合自定义的<code>HandlerHeartBeat</code>处理器，监听到读超时事件后主动关闭无效连接，避免资源泄漏；写超时暂时设为 0（心跳由客户端主动发，服务端仅被动检测），如果检测到写超时也会主动推送心跳包维持连接。</li></ul></li><li><strong>高性能优化</strong>：拆分 Boss/Worker 线程组（Boss 组仅处理连接建立，Worker 组处理消息读写），避免连接和消息处理互相阻塞；给业务 Handler 加<code>@Sharable</code>注解复用实例，减少对象创建开销；容器销毁时通过<code>@PreDestroy</code>优雅关闭 EventLoopGroup，防止端口占用 / 线程泄漏。</li></ul><h4 id="Redisson-缓存会话-amp-用户路由（解决集群状态同步）"><a href="#Redisson-缓存会话-amp-用户路由（解决集群状态同步）" class="headerlink" title="Redisson 缓存会话 &amp; 用户路由（解决集群状态同步）"></a>Redisson 缓存会话 &amp; 用户路由（解决集群状态同步）</h4><p>单节点下可以用本地 ConcurrentMap 维护用户 ID 和 Netty Channel 的映射，但集群环境下节点间隔离，必须用分布式缓存 / 广播：</p><ul><li><p>缓存设计：会话列表缓存：把用户的聊天会话（ChatSessionUser）缓存到 Redis，用户上线时直接从 Redis 拉取，比每次查数据库减少 80% 以上的加载耗时；</p><p>用户路由：用 Redis 维护「用户 ID - 节点标识 - Channel」的映射，同时基于 Redisson 的<code>RTopic</code>实现集群消息广播 —— 比如 A 节点的用户给 B 节点的用户发消息，先通过 RTopic 把消息广播到所有节点，节点收到后查本地 ChannelMap，找到目标用户则推送消息，解决集群下消息路由问题；</p><p>状态维护：用户上线 / 下线 / 心跳都会更新 Redis 中的状态（比如<code>saveUserHeartBeat</code>记录心跳时间，<code>removeUserHeartBeat</code>清理离线状态），集群节点可通过 Redis 感知用户在线状态，避免给离线用户推送无效消息。</p></li><li><p><strong>选型思考</strong>：选 Redisson 而非原生 Redis 客户端，是因为它封装了分布式锁、ChannelGroup、Topic 等适配 Netty 的特性，且支持 Redis 集群模式，能直接对接生产环境的 Redis 集群。</p></li></ul><h4 id="离线消息增量拉取（保证消息有序不丢失）"><a href="#离线消息增量拉取（保证消息有序不丢失）" class="headerlink" title="离线消息增量拉取（保证消息有序不丢失）"></a>离线消息增量拉取（保证消息有序不丢失）</h4><p>用户重连后如果全量拉取所有消息，不仅性能差，还会导致用户体验下降，所以设计了增量拉取逻辑：</p><ul><li><strong>核心锚点</strong>：在用户表（UserInfo）中记录<code>lastOffTime</code>（最后离线时间戳），用户重连时以此为基准拉取增量数据；</li><li><strong>增量范围控制</strong>：考虑到用户离线过久（比如超过 3 天）会产生大量消息，限制仅拉取最近 3 天的增量，平衡性能和用户体验；</li><li>增量维度：<ul><li>会话列表：按<code>last_receive_time &gt; lastOffTime</code>筛选离线期间有新消息的会话，保证会话最新；</li><li>聊天消息：按联系人维度（用户 / 群聊）筛选离线期间新增的消息，且按接收时间降序排序，确保消息有序；</li><li>好友申请：筛选离线期间新增的未处理申请，统计数量并推送给用户；</li></ul></li><li><strong>实现细节</strong>：将这些增量数据封装成<code>WsInitData</code>对象，在用户握手成功后一次性推送，既保证消息不丢失，又避免全量加载的性能问题。</li></ul><h4 id="IM系统保证消息的可靠（有序、不丢失、不重复）"><a href="#IM系统保证消息的可靠（有序、不丢失、不重复）" class="headerlink" title="IM系统保证消息的可靠（有序、不丢失、不重复）"></a>IM系统保证消息的可靠（有序、不丢失、不重复）</h4><p>客户端 A 向 B 发送消息要实现<strong>绝对可靠</strong>（不丢、不重、有序、可达），需要覆盖「发送端→服务端→接收端」全链路的确认机制、异常兜底和存储保障。结合你现有 Netty+Redisson/Redis 的技术栈，以下是可落地的核心方案（补充现有离线增量拉取、集群广播之外的可靠性保障）：</p><p>消息可靠的本质是「全链路确认 + 多层兜底」：</p><ul><li>不丢：每一步操作都有「确认回执」+「持久化兜底」；</li><li>不重：全局唯一 ID + 幂等校验；</li><li>有序：基于时序 / 唯一 ID 的排序机制；</li><li>可达：离线兜底 + 重试机制。</li></ul><p>发送端（A）：避免「发出去就丢」</p><p>客户端本地缓存 + 重试机制</p><ul><li><strong>问题</strong>：A 发送消息时网络闪断 / 服务端暂不可达，消息直接丢失。</li><li>实现：A 发送消息前，先将消息（带<strong>全局唯一 msgId</strong>，雪花算法生成）缓存到本地（移动端用 SP/MMKV，PC 端用本地 DB），标记状态为「待发送」；<ul><li>监听 Netty writeAndFlush的 Future 回调：<ul><li>成功（收到服务端「接收 ACK」）：更新本地状态为「已发送」；</li><li>失败 / 超时：触发<strong>指数退避重试</strong>（1s→3s→5s，最多 5 次），重试失败则标记「发送失败」，给用户弹窗提示手动重发。</li></ul></li></ul></li></ul><h5 id="服务端「接收确认」（第一层兜底）"><a href="#服务端「接收确认」（第一层兜底）" class="headerlink" title="服务端「接收确认」（第一层兜底）"></a>服务端「接收确认」（第一层兜底）</h5><p><strong>问题</strong>：服务端收到 A 的消息但未处理就宕机，消息丢失。</p><p>只有DB 写入成功，才给 A 返回 ACK；否则告知 A 发送失败，触发客户端重试。</p><h4 id="服务端转发阶段：避免「服务端收到但-B-收不到」"><a href="#服务端转发阶段：避免「服务端收到但-B-收不到」" class="headerlink" title="服务端转发阶段：避免「服务端收到但 B 收不到」"></a>服务端转发阶段：避免「服务端收到但 B 收不到」</h4><h5 id="消息持久化-投递状态跟踪"><a href="#消息持久化-投递状态跟踪" class="headerlink" title="消息持久化 + 投递状态跟踪"></a>消息持久化 + 投递状态跟踪</h5><ul><li><strong>问题</strong>：服务端已接收消息，但 B 离线 / Channel 断开，消息无法投递。</li><li>实现：消息落地到<code>chat_message</code>表时，增加<code>message_status</code>字段（<code>TO_DELIVER</code>待投递 /<code>DELIVERED</code>已投递 /<code>READ</code>已读）；</li></ul><h5 id="集群环境：可靠广播替代普通-Topic"><a href="#集群环境：可靠广播替代普通-Topic" class="headerlink" title="集群环境：可靠广播替代普通 Topic"></a>集群环境：可靠广播替代普通 Topic</h5><ul><li><strong>问题</strong>：现有<code>RTopic</code>是普通广播，若消费节点宕机，消息直接丢失。</li><li>实现：用 Redisson <code>ReliableTopic</code>（可靠 Topic）替代普通<code>RTopic</code></li></ul><h4 id="接收端（B）：避免「收到但没确认」"><a href="#接收端（B）：避免「收到但没确认」" class="headerlink" title="接收端（B）：避免「收到但没确认」"></a>接收端（B）：避免「收到但没确认」</h4><h5 id="（1）接收-已读确认机制（回执）"><a href="#（1）接收-已读确认机制（回执）" class="headerlink" title="（1）接收 / 已读确认机制（回执）"></a>（1）接收 / 已读确认机制（回执）</h5><ul><li><strong>问题</strong>：B 收到消息但服务端不知情，A 误以为消息丢失，重复发送。</li><li>实现：<ul><li>B 收到消息后，立即给服务端返回「已接收 ACK」（携带 msgId）；</li><li>服务端收到 ACK 后，更新 DB/Redis 中消息状态为「DELIVERED」，并推送给 A「B 已收到」的回执；</li><li>B 进入聊天窗口阅读消息后，再返回「已读 ACK」，服务端更新状态为「READ」，同步给 A（已读回执）</li></ul></li></ul><h5 id="（2）幂等去重：避免重复消息"><a href="#（2）幂等去重：避免重复消息" class="headerlink" title="（2）幂等去重：避免重复消息"></a>（2）幂等去重：避免重复消息</h5><ul><li><strong>问题</strong>：网络重试 / 集群广播导致 B 收到重复消息。</li><li>实现：全局唯一 msgId：所有消息用雪花算法生成唯一 ID（包含用户 ID、时间戳、序列号）；<ul><li>客户端 B：本地维护「已接收 msgId 列表」（内存 + 本地缓存），收到消息先校验 msgId 是否存在，存在则丢弃；</li><li>服务端：Redis 中维护<code>msg_delivered_set:{userId}</code>（已投递 msgId 集合），推送前先校验，避免重复推送。</li></ul></li></ul><p>极端场景：兜底保障</p><h5 id="（1）死信队列（DLQ）：处理多次重试失败的消息"><a href="#（1）死信队列（DLQ）：处理多次重试失败的消息" class="headerlink" title="（1）死信队列（DLQ）：处理多次重试失败的消息"></a>（1）死信队列（DLQ）：处理多次重试失败的消息</h5><ul><li><strong>问题</strong>：B 长期离线 / 账号异常，消息重试 N 次仍无法投递，导致消息积压 / 丢失。</li><li>实现：<ul><li>消息重试≥5 次仍失败，转入死信队列<code>DLQ_QUEUE</code>；</li><li>后台定时任务扫描死信队列，做兜底处理：<ul><li>给 A 推送「消息投递失败」提示；</li><li>若 B 是离线状态，保留消息到 DB，待 B 上线后强制触发增量拉取；</li><li>死信消息保留 7 天，避免永久丢失。</li></ul></li></ul></li></ul><h5 id="（2）分布式事务：保障「存储-投递」原子性"><a href="#（2）分布式事务：保障「存储-投递」原子性" class="headerlink" title="（2）分布式事务：保障「存储 + 投递」原子性"></a>（2）分布式事务：保障「存储 + 投递」原子性</h5><ul><li><p><strong>问题</strong>：服务端将消息存入 DB，但推送时失败，导致「存了但没发」。</p></li><li><p>实现：基于「本地消息表 + 定时任务」实现最终一致性：</p><ol><li><p>服务端接收 A 的消息时，同时写入「本地消息表」（标记「待投递」）；</p></li><li><p>定时任务扫描本地消息表，重推「待投递」且超过阈值（比如 10s）的消息；</p></li><li><p>推送成功后更新状态为「已投递」，避免重复扫描。</p></li></ol></li></ul><h4 id="心跳机制为什么设-60-秒读超时？写超时为什么设-0？"><a href="#心跳机制为什么设-60-秒读超时？写超时为什么设-0？" class="headerlink" title="心跳机制为什么设 60 秒读超时？写超时为什么设 0？"></a>心跳机制为什么设 60 秒读超时？写超时为什么设 0？</h4><p>答：读超时设 60 秒是结合业务场景（即时通讯不需要超高频心跳），平衡「连接保活」和「资源占用」—— 太短会增加客户端心跳开销，太长会导致无效连接占用端口 / 线程；写超时设 0 是因为我们设计的是「客户端主动发心跳，服务端被动检测」，如果服务端主动推心跳（写超时触发），会增加服务端开销，且客户端如果在线，自然会主动发心跳，无需服务端额外推送。</p><h4 id="集群下怎么保证消息不推错、不重复？"><a href="#集群下怎么保证消息不推错、不重复？" class="headerlink" title="集群下怎么保证消息不推错、不重复？"></a>集群下怎么保证消息不推错、不重复？</h4><p>方法一：首先通过 Redisson RTopic 广播消息时，会携带「目标用户 ID」，每个节点收到广播后，先查本地的 USER_CONTEXT_MAP（ConcurrentMap），只有本地有该用户的 Channel 才会推送，避免推错；其次消息推送前会结合 Redis 的用户在线状态做校验（比如用户已离线则不推送），且聊天消息入库时会加唯一 ID，客户端收到后去重，避免重复推送。</p><h4 id="离线消息增量拉取怎么保证有序？"><a href="#离线消息增量拉取怎么保证有序？" class="headerlink" title="离线消息增量拉取怎么保证有序？"></a>离线消息增量拉取怎么保证有序？</h4><p>数据库层面给<code>chat_message</code>表的<code>last_receive_time</code>字段加了索引，查询时按<code>last_receive_time desc</code>排序，拉取后推送给客户端时也是按这个顺序；另外会话列表也按最后接收消息时间降序排列，保证用户重连后看到的消息 / 会话和离线前的顺序一致，符合用户的使用习惯。</p><h1 id="文件分片上传、断点续传"><a href="#文件分片上传、断点续传" class="headerlink" title="文件分片上传、断点续传"></a>文件分片上传、断点续传</h1><p>可靠性、有序性、不重复</p><h1 id="RAG检索流水线"><a href="#RAG检索流水线" class="headerlink" title="RAG检索流水线"></a>RAG检索流水线</h1><p>按 “数据侧 + 查询侧” 梳理</p><p>核心流程分两大环节：</p><ol><li>离线数据预处理（语料入库）：   - 垂直领域语料清洗（去重/降噪/结构化）→ 拆分chunk（按语义/固定长度，比如512token）；   - 双路编码：① 用领域微调的Bi-Encoder（如BERT-base）将chunk转为768维向量；② 对chunk做中文分词（IK分词器），提取关键词/实体；   - ES索引构建：创建混合索引（text类型存文本+dense_vector类型存向量），同时优化BM25参数、向量索引类型。 2. 在线检索（问答推理）：</li><li>用户问题输入 → 同样做Bi-Encoder向量化+关键词提取；   - 多路召回：① KNN向量检索（top100）+ ② BM25关键词检索（top100）；   - 结果去重/合并（保留top100）→ Cross-Encoder重排（对“问题+候选chunk”打分）→ 取top10作为检索结果；   - 把top10 chunk拼接为prompt，送入大模型生成回答 → 返回最终结果。</li></ol><h1 id="文件解析与切片、向量入库"><a href="#文件解析与切片、向量入库" class="headerlink" title="文件解析与切片、向量入库"></a>文件解析与切片、向量入库</h1><h2 id="文档预处理（统一格式-噪声清洗）"><a href="#文档预处理（统一格式-噪声清洗）" class="headerlink" title="文档预处理（统一格式 + 噪声清洗）"></a>文档预处理（统一格式 + 噪声清洗）</h2><p>先将不同格式（PDF/Word/Markdown/TXT）的文档标准化，同时过滤无意义内容，避免干扰分片逻辑：</p><h3 id="多格式文本提取"><a href="#多格式文本提取" class="headerlink" title="多格式文本提取"></a>多格式文本提取</h3><div class="table-container"><table><thead><tr><th style="text-align:center">文档格式</th><th style="text-align:center">提取方式（核心工具 / 逻辑）</th></tr></thead><tbody><tr><td style="text-align:center">Word（DOCX/DOC）</td><td style="text-align:center">解析 XML 结构，提取「样式层级（标题 1/2/3）、正文段落、文本块」（工具：POI）；</td></tr><tr><td style="text-align:center">PDF</td><td style="text-align:center">文本型 PDF：Apache PDFBox/IText 提取文本块，按字体大小 / 行距 / 缩进识别标题；图片型 PDF：先 OCR（Tesseract）转文本，再按文本块拆分；</td></tr><tr><td style="text-align:center">Markdown</td><td style="text-align:center">解析<code>#/##/###</code>标题标识、空行、列表符，识别章节 / 段落边界；</td></tr><tr><td style="text-align:center">TXT</td><td style="text-align:center">按换行符、特殊分隔符（如<code>====</code>/<code>----</code>）识别章节，保留原始文本结构；</td></tr></tbody></table></div><p>实现基于⽂档层级树的分层索引⽅案，通过“章-段-句”组织结构并以句⼦为最小向量单元，解决常规硬切⽚ 导致的语义链断裂问题。同时集成⽗⼦索引策略实现“细粒度检索、宽粒度召回”，以解决⻓⽂档问答中的信 息碎⽚化问题。</p><h3 id="2-噪声清洗"><a href="#2-噪声清洗" class="headerlink" title="2. 噪声清洗"></a>2. 噪声清洗</h3><ul><li>正则过滤：移除页眉页脚（如 “XX 白皮书 第 X 页”）、水印、重复空行、无意义符号（如<code>★/■</code>）；</li><li>特殊文本处理：表格 / 公式 / 代码块标记为 “特殊单元”，暂不拆分（后续单独处理）。</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>目标：识别 “章 / 节” 边界，构建层级树顶层，核心是 “标题规则 + 主题连续性”。</p><h4 id="1-章节边界判定规则（优先级从高到低）"><a href="#1-章节边界判定规则（优先级从高到低）" class="headerlink" title="1. 章节边界判定规则（优先级从高到低）"></a>1. 章节边界判定规则（优先级从高到低）</h4><div class="table-container"><table><thead><tr><th style="text-align:center">规则类型</th><th style="text-align:center">具体逻辑</th></tr></thead><tbody><tr><td style="text-align:center">格式规则</td><td style="text-align:center">- 一级标题：Word “标题 1”、Markdown<code>#</code>、PDF 字体≥16 号且加粗、TXT “第 X 章”；- 二级 / 三级节：Word“标题 2/3”、Markdown<code>##/###</code>、PDF 字体≥14 号；</td></tr><tr><td style="text-align:center">主题规则</td><td style="text-align:center">无格式标题时，用 NLP 主题模型（如 LDA）分析文本主题，<strong>主题突变处判定为新章节</strong>；</td></tr><tr><td style="text-align:center">兜底规则</td><td style="text-align:center">超长无标题文档：按 “每 5000 字（可配置）” 拆分虚拟章节（标记为 “未命名章节 X”）；</td></tr></tbody></table></div><h4 id="章节节点生成"><a href="#章节节点生成" class="headerlink" title=". 章节节点生成"></a>. 章节节点生成</h4><p>遍历预处理后的文本，生成章节节点，结构示例：</p><p>java</p><p>运行</p><figure class="highlight haxe"><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="comment">// 章节节点核心结构</span></span><br><span class="line"><span class="title class_"><span class="keyword">class</span> <span class="title">ChapterNode</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">String</span> chapterId;       <span class="comment">// 唯一ID（如doc_1_chapter_1）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">String</span> parentChapterId; <span class="comment">// 父章节ID（顶级章为null）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">String</span> title;           <span class="comment">// 章节标题（如“1.1 人工智能发展历程”）</span></span><br><span class="line">    <span class="keyword">private</span> Long startPos;          <span class="comment">// 文本起始位置（用于溯源）</span></span><br><span class="line">    <span class="keyword">private</span> Long endPos;            <span class="comment">// 文本结束位置</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;<span class="keyword">String</span>&gt; paragraphIds; <span class="comment">// 子段落ID列表（初始为空）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>关联逻辑：将 “标题后→下一个标题前” 的所有正文，绑定为该章节的 “待分片段落内容”。</li></ul><h3 id="步骤-2：段落级分片（树的「中间节点」）"><a href="#步骤-2：段落级分片（树的「中间节点」）" class="headerlink" title="步骤 2：段落级分片（树的「中间节点」）"></a>步骤 2：段落级分片（树的「中间节点」）</h3><p>目标：拆分章节下的正文为段落，核心是 “格式边界 + 语义边界” 双判定。</p><h4 id="1-段落边界判定规则"><a href="#1-段落边界判定规则" class="headerlink" title="1. 段落边界判定规则"></a>1. 段落边界判定规则</h4><p>表格</p><div class="table-container"><table><thead><tr><th style="text-align:center">规则类型</th><th style="text-align:center">具体逻辑</th></tr></thead><tbody><tr><td style="text-align:center">格式边界（优先）</td><td style="text-align:center">Word 段落标记、PDF 文本块分隔、Markdown/TXT 空行（连续≥1 个换行）；</td></tr><tr><td style="text-align:center">语义边界（补充）</td><td style="text-align:center">无格式分隔时，按 “连续文本 + 换行 + 非缩进文本” 判定段落结束；用 HanLP/LTP 的「分词 + 依存句法分析」，确保段落语义完整；</td></tr></tbody></table></div><h4 id="2-段落节点生成"><a href="#2-段落节点生成" class="headerlink" title="2. 段落节点生成"></a>2. 段落节点生成</h4><p>遍历章节下的正文，拆分段落并关联到父章节，结构示例：</p><figure class="highlight arduino"><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="comment">// 段落节点核心结构</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ParagraphNode</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> paragraphId;    <span class="comment">// 唯一ID（如doc_1_chapter_1_para_1）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> chapterId;      <span class="comment">// 父章节ID（关联顶层）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> text;           <span class="comment">// 段落文本</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;<span class="type">String</span>&gt; sentenceIds; <span class="comment">// 子句子ID列表（初始为空）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> prevParaId;     <span class="comment">// 前一段落ID（上下文回溯）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> nextParaId;     <span class="comment">// 后一段落ID</span></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><p>目标：拆分段落为句子（最小语义单元），核心是 “句终结符 + 语义完整性校正”。</p><h4 id="1-句子边界判定规则（避免语义割裂）"><a href="#1-句子边界判定规则（避免语义割裂）" class="headerlink" title="1. 句子边界判定规则（避免语义割裂）"></a>1. 句子边界判定规则（避免语义割裂）</h4><div class="table-container"><table><thead><tr><th style="text-align:center">基础规则</th><th style="text-align:center">具体逻辑</th></tr></thead><tbody><tr><td style="text-align:center">句终结符</td><td style="text-align:center">中文：<code>。！？；</code>；英文：<code>. ! ?</code>；特殊处理：省略号<code>……</code>、引号内句子不拆分；</td></tr><tr><td style="text-align:center">语义校正规则</td><td style="text-align:center">- 用 HanLP 的「句法分析」检查：如 “因为 A，所以 B。” 不能拆分为 “因为 A，” 和 “所以 B。”；- 过滤无意义短句（如仅 “注：”“补充：”）；- 对话拆分：“小明：你好！小红：再见！”→拆为 2 个句子；</td></tr></tbody></table></div><h4 id="2-句子节点生成（最小向量单元）"><a href="#2-句子节点生成（最小向量单元）" class="headerlink" title="2. 句子节点生成（最小向量单元）"></a>2. 句子节点生成（最小向量单元）</h4><p>遍历段落文本，拆分句子并关联到父段落，结构示例：</p><figure class="highlight arduino"><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="comment">// 句子节点核心结构（最小向量单元）</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SentenceNode</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> sentenceId;     <span class="comment">// 唯一ID（如doc_1_chapter_1_para_1_sent_1）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> paragraphId;    <span class="comment">// 父段落ID（关联中间层）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> chapterId;      <span class="comment">// 父章节ID（跨层关联）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> text;           <span class="comment">// 句子文本（完整语义）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">float</span>[] vector;        <span class="comment">// 句子向量（后续嵌入生成）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> prevSentId;     <span class="comment">// 前一句子ID（上下文回溯）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> nextSentId;     <span class="comment">// 后一句子ID</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="难点-1：常规硬切片导致的语义链断裂"><a href="#难点-1：常规硬切片导致的语义链断裂" class="headerlink" title="难点 1：常规硬切片导致的语义链断裂"></a>难点 1：常规硬切片导致的语义链断裂</h4><ul><li>核心痛点：传统硬切片（按固定字符数 / 行数切分）会割裂完整语义单元，比如一个因果句 “因为 A 技术高并发，所以 B 场景选 A” 被拆成两段，检索 “B 场景选 A 的原因” 时，仅能匹配到后半段，丢失 “高并发” 核心语义，导致问答结果缺失关键信息。</li><li>解决方案：<ol><li>层级化语义拆分：放弃无意义硬切片，基于文档原生结构（章标题、段分隔符、句终结符「。！？」）构建 “章 - 段 - 句” 层级树，确保每个层级都贴合自然语义边界；</li><li>最小语义单元锁定：以 “句子” 为最小向量单元生成嵌入向量，保证单个向量承载完整语义（如上述因果句作为一个向量单元），从底层避免语义链断裂；</li><li>上下文关联存储：每个句子向量额外存储「父节点（段落 ID / 章节 ID）+ 前后句 ID」元数据，即使检索到单个句子，也能快速回溯到所属段落 / 章节，补充完整语义链。</li></ol></li></ul><h4 id="长文档问答的信息碎片化"><a href="#长文档问答的信息碎片化" class="headerlink" title="长文档问答的信息碎片化"></a>长文档问答的信息碎片化</h4><ul><li><p>核心痛点：长文档按句子级细粒度检索时，能精准匹配到相关句子，但结果是孤立的 “信息碎片”，无法整合出有逻辑的完整答案；若按章节级粗粒度检索，召回范围过宽，精准度极低。</p></li><li><p>解决方案（父子索引策略）：</p><ol><li><p>索引分层设计：</p><ul><li>子索引（细粒度）：存储句子级向量 + 语义元数据，负责 “细粒度检索”—— 精准定位用户问题对应的核心句子；</li><li>父索引（宽粒度）：对段落 / 章节内所有句子向量做均值 / 加权聚合，生成段落 / 章节级聚合向量，负责 “宽粒度召回”—— 召回核心句子所属的完整段落 / 章节；</li></ul></li><li><p>检索协作流程</p><p>：① 用户提问→子索引检索 Top-N 相似句子→获取句子所属段落 / 章节 ID；</p><p>② 基于章节 ID 在父索引中召回完整段落 / 章节内容（宽粒度）；</p><p>③ 以宽粒度内容为上下文，整合细粒度句子，生成完整问答答案，解决信息碎片化；</p></li><li><p>权重调优：父索引召回时，基于子索引匹配句子的相似度权重对父节点排序，优先召回核心句子所属的父节点，保证宽粒度内容与问题强相关。</p></li></ol></li></ul><h4 id="多格式文档的层级树构建精准性"><a href="#多格式文档的层级树构建精准性" class="headerlink" title="多格式文档的层级树构建精准性"></a>多格式文档的层级树构建精准性</h4><ul><li><p>核心痛点：PDF（无显性段落标记）、Word（样式层级）、Markdown（# 标识）等不同格式文档的 “章 - 段 - 句” 边界差异大，导致层级拆分不精准；长文档层级树节点过多，索引构建 / 检索效率低。</p></li><li><p>解决方案</p><ol><li><p>多格式结构化解析</p><p>：PDF：通过文本块 + 行距分析识别段落，结合 OCR 处理图片文本后再拆分句子；</p><ul><li>Word：解析样式层级（标题 1→章、标题 2→节、正文→段），按样式拆分层级；</li><li>Markdown：通过 #/##/### 识别章节，按换行符拆分段落；</li><li>引入 NLP 工具（HanLP/Jieba）做句法分析，校正拆分错误（如省略号误判为句终结符）；</li></ul></li><li><p>层级树轻量化</p><p>：过滤页眉页脚、免责声明等无意义节点，对超长章节（万字以上）在 “段” 层级做二次语义拆分（保证段落语义完整），减少节点数量；</p></li><li><p>增量索引更新</p><p>：记录各层级节点的修改时间戳，仅对更新的章 / 段 / 句重新生成向量并更新索引，而非全量重建。</p></li></ol></li></ul><h4 id="细粒度检索-宽粒度召回的效率平衡"><a href="#细粒度检索-宽粒度召回的效率平衡" class="headerlink" title="细粒度检索 + 宽粒度召回的效率平衡"></a>细粒度检索 + 宽粒度召回的效率平衡</h4><ul><li><p>核心痛点：句子级检索需遍历大量向量，章节级召回需关联父节点，双重检索导致延迟升高；向量数据库性能随数据量增长下降。</p></li><li><p>解决方案：</p><ol><li><p>预关联映射表：</p><p>提前构建「句子 ID→段落 ID→章节 ID」映射表，存储在向量数据库元数据中，检索时无需额外查询，直接关联父节点；</p></li><li><p>向量数据库分层优化：</p><ul><li>选型：用 Milvus（支持分区）按章节分区存储父 / 子索引；</li><li>索引类型：子索引用 IVF_FLAT（保证精度），父索引用 HNSW（提升速度）；</li></ul></li><li><p>结果截断与缓存</p><p>：细粒度检索仅返回 Top-10 相似句子，减少关联父节点的开销；对高频查询的章节向量做缓存，降低重复检索成本。</p></li></ol></li></ul><h1 id="知识库检索"><a href="#知识库检索" class="headerlink" title="知识库检索"></a>知识库检索</h1><p>构建基于 Elasticsearch 的 RAG 检索流⽔线，通过集成 KNN 向量检索与 BM25 关键词匹配实现多路召回， 并引⼊ Cross-Encoder 重排模型以优化语义对⻬精度，在平衡系统响应性能的同时将垂直领域问答准确率提 升25% 以上。</p><h4 id="什么选择-KNN-BM25-多路召回，而非单一检索方式？"><a href="#什么选择-KNN-BM25-多路召回，而非单一检索方式？" class="headerlink" title="什么选择 KNN+BM25 多路召回，而非单一检索方式？"></a>什么选择 KNN+BM25 多路召回，而非单一检索方式？</h4><p><strong>回答思路</strong>（对比单一方式的痛点，突出互补性）：</p><p>垂直领域（比如金融/医疗）的问答有两个核心诉求：精准匹配专业术语 + 语义理解上下文，单一方式无法兼顾：</p><ul><li>仅用BM25：依赖关键词匹配，容易出现“词不达意”（比如用户问“糖尿病血糖控制”，BM25可能漏检含“降糖”但无“血糖控制”的语料），语义召回率低；</li><li>仅用KNN向量检索：对专业术语的精准匹配弱（比如“重疾险”和“重大疾病保险”向量相似但关键词完全不同时，可能漏检），且向量检索性能成本更高；</li><li>多路召回：BM25保证“关键词精准性”，KNN保证“语义相关性”，两者互补覆盖更多候选结果，为后续重排提供足够的优质样本。</li></ul><h4 id="Cross-Encoder-重排的作用是什么？和-Bi-Encoder-的区别？为什么选它？"><a href="#Cross-Encoder-重排的作用是什么？和-Bi-Encoder-的区别？为什么选它？" class="headerlink" title="Cross-Encoder 重排的作用是什么？和 Bi-Encoder 的区别？为什么选它？"></a>Cross-Encoder 重排的作用是什么？和 Bi-Encoder 的区别？为什么选它？</h4><p>作用：解决多路召回结果中“语义匹配精度低”的问题——<strong>召回阶段为了兼顾召回率会返回较多候选，但部分候选和问题的语义匹配度低，Cross-Encoder通过“问题+候选文本”的成对打分</strong>，精准排序出最相关的结果。 </p><p>与Bi-Encoder的区别：   - Bi-Encoder：单文本编码（问题/候选分别编码），速度快但精度低（编码时无交互），适合召回阶段；   - Cross-Encoder：成对编码（问题和候选拼接后编码），精度高但速度慢（无法预编码），适合重排阶段； 3. 选择原因：垂直领域对问答准确率要求高，且召回后候选集已压缩到100条内，Cross-Encoder的性能损耗可控，能显著提升语义对齐精度。</p><h4 id="在-ES-中是如何实现-KNN-向量检索的？用到了哪些-ES-特性？"><a href="#在-ES-中是如何实现-KNN-向量检索的？用到了哪些-ES-特性？" class="headerlink" title="在 ES 中是如何实现 KNN 向量检索的？用到了哪些 ES 特性？"></a>在 ES 中是如何实现 KNN 向量检索的？用到了哪些 ES 特性？</h4><p><strong>回答思路</strong>（结合 ES 版本 / 索引类型 / 参数，体现实操）：</p><p>我们用的是ES 8.x版本（原生支持dense_vector和KNN），核心实现细节：</p><ol><li>向量字段定义：创建索引时指定dense_vector类型，参数设置：<br>“vector”: {<br>  “type”: “dense_vector”,<br>  “dims”: 768,  // 与Bi-Encoder输出维度一致<br>  “index”: true,<br>  “similarity”: “cosine”  // 垂直领域语义匹配用余弦相似度更合适<br>}</li><li>向量索引类型：选择HNSW（Hierarchical Navigable Small Worlds），相比brute-force暴力检索，HNSW在召回率（&gt;95%）和性能（QPS提升3倍）间更平衡，关键参数调优：<ul><li>m: 16（邻接节点数，平衡召回率/内存）；</li><li>ef_construction: 100（构建索引时的探索深度）；</li><li>ef_search: 50（检索时的探索深度）；</li></ul></li><li>检索语法：用ES的knn查询子句，指定字段、查询向量、k值（top100），和BM25查询通过bool should子句实现多路召回。</li></ol><h4 id="多路召回的结果是如何合并-去重的？有没有做权重调整？"><a href="#多路召回的结果是如何合并-去重的？有没有做权重调整？" class="headerlink" title="多路召回的结果是如何合并 / 去重的？有没有做权重调整？"></a>多路召回的结果是如何合并 / 去重的？有没有做权重调整？</h4><p>合并逻辑分三步： 1. 去重：基于chunk的唯一ID去重（离线处理时为每个chunk生成唯一ID），避免同一语料被BM25和KNN同时召回； 2. 初步排序：给两路召回结果赋初始权重（BM25得分<em>0.4 + KNN相似度</em>0.6），先做一次粗排（<strong>垂直领域语义更重要，所以KNN权重更高</strong>）； 3. 截断：保留top100候选（既保证召回率，又控制后续Cross-Encoder的计算成本）； 补充：权重比例是通过离线实验调优的——测试了0.3:0.7、0.4:0.6、0.5:0.5三组比例，最终0.4:0.6在召回率（&gt;90%）和准确率上最优。</p><h4 id="Cross-Encoder-重排是如何集成到流程中的？如何平衡性能？"><a href="#Cross-Encoder-重排是如何集成到流程中的？如何平衡性能？" class="headerlink" title="Cross-Encoder 重排是如何集成到流程中的？如何平衡性能？"></a>Cross-Encoder 重排是如何集成到流程中的？如何平衡性能？</h4><p>集成方式（微服务架构）：   - 检索层（ES）返回top100候选后，调用“重排服务”（基于FastAPI封装Cross-Encoder）；   - 重排服务输入：用户问题 + 100条候选chunk；输出：按Cross-Encoder打分降序的top10 chunk；   - 重排服务做了模型量化（INT8），部署在GPU上（batch推理），单条推理耗时从20ms降到5ms；</p><p> 2性能平衡的关键优化：   - 候选集截断：仅对召回的top100做重排，而非全量；   - 模型轻量化：选用轻量版Cross-Encoder（如cross-encoder/ms-marco-MiniLM-L-6-v2），而非大模型；   - 缓存：对高频问题（比如Top1000常见问题）的检索+重排结果做缓存（Redis），命中率约30%，整体响应时间从500ms降到200ms内。</p><h4 id="准确率提升-25-”-是如何量化的？对比的基线是什么？"><a href="#准确率提升-25-”-是如何量化的？对比的基线是什么？" class="headerlink" title="准确率提升 25%” 是如何量化的？对比的基线是什么？"></a>准确率提升 25%” 是如何量化的？对比的基线是什么？</h4><p>1.评测数据集：构建了垂直领域（比如医疗）的评测集，包含1000个真实用户问题 + 人工标注的“标准答案chunk”（每个问题对应3-5个核心chunk）； </p><ol><li>核心指标：   - 检索准确率（Recall@10）：检索结果中包含至少1个标准答案chunk的比例（核心指标，因为RAG的关键是召回正确语料）；   - 问答准确率：人工评估大模型生成回答与标准答案的匹配度（分为精准/部分/错误三档）； </li><li><ol><li>对比基线：仅用BM25检索的RAG流程； 4. 结果：   - 基线：检索Recall@10=60%，问答准确率=65%；   - 优化后：检索Recall@10=82%，问答准确率=81%（提升24.6%，约25%）；   - 同时，通过缓存/模型量化，系统响应时间控制在200ms内（满足线上要求的300ms阈值）。</li></ol></li></ol><h4 id="KNN-向量检索的性能瓶颈是什么？你是如何优化的？"><a href="#KNN-向量检索的性能瓶颈是什么？你是如何优化的？" class="headerlink" title="KNN 向量检索的性能瓶颈是什么？你是如何优化的？"></a>KNN 向量检索的性能瓶颈是什么？你是如何优化的？</h4><p>落地初期的瓶颈： 1. 向量索引构建耗时久（百万级chunk构建需数小时）； </p><ol><li><p>在线检索QPS低（单节点QPS仅50），延迟高（平均300ms）； 优化手段： 1. 索引层面：   - 向量维度优化：从1024维降到768维（通过领域微调验证，召回率仅下降1%，检索速度提升20%）； </p></li><li><p>ES集群调优：增加分片数（按数据量设8分片），副本数1（兼顾高可用和检索性能），关闭刷新间隔（离线构建索引时）； </p><p>工程层面：   - 批量编码：离线语料向量化时用batch_size=64批量处理，提升编码效率；   - 预热：上线前预热ES索引（提前查询），避免冷启动时的磁盘IO瓶颈； 最终：索引构建时间降到1小时内，在线检索QPS提升到200+，延迟降到80ms内。</p></li></ol><h4 id="落地过程中最大的挑战是什么？如何解决的？"><a href="#落地过程中最大的挑战是什么？如何解决的？" class="headerlink" title="落地过程中最大的挑战是什么？如何解决的？"></a>落地过程中最大的挑战是什么？如何解决的？</h4><p>最大挑战：Cross-Encoder重排后准确率提升，但系统响应时间从150ms涨到500ms，超出线上300ms的阈值。 分析过程：</p><p>拆解耗时：ES检索80ms + Cross-Encoder重排350ms + 大模型推理70ms → 重排是核心瓶颈； - 根因：用了全量Cross-Encoder（bert-base），单条推理20ms，100条候选就是2000ms（批量后仍350ms）； </p><p>解决措施： 1. 模型轻量化：替换为MiniLM-L6版Cross-Encoder，推理耗时从20ms/条降到5ms/条；</p><ol><li>候选集再压缩：基于“BM25+KNN”的粗排得分，将候选集从100条降到50条（验证召回率仅下降2%）；</li><li>缓存高频问题：对Top1000高频问题的检索+重排结果缓存，命中率30%； 最终结果：重排耗时降到80ms，整体响应时间控制在200ms内，同时问答准确率仍保持提升25%的效果。</li></ol><h4 id="向量检索和-BM25-的结果冲突时（比如-KNN-认为相关，BM25-认为不相关），如何处理？"><a href="#向量检索和-BM25-的结果冲突时（比如-KNN-认为相关，BM25-认为不相关），如何处理？" class="headerlink" title="向量检索和 BM25 的结果冲突时（比如 KNN 认为相关，BM25 认为不相关），如何处理？"></a>向量检索和 BM25 的结果冲突时（比如 KNN 认为相关，BM25 认为不相关），如何处理？</h4><p>做了“场景化权重调整+人工规则兜底”：</p><ol><li>基础规则：对含专业术语的问题（通过实体识别判断），提升BM25权重（比如0.5:0.5），保证术语精准匹配；对泛语义问题（比如“如何缓解高血压头晕”），提升KNN权重（0.3:0.7）；</li><li>冲突过滤：如果某条候选在KNN中排前20，但BM25得分是0（完全无关键词匹配），或反之，会标记为“冲突候选”，仅保留前50%的冲突候选，避免低质量结果进入重排； </li><li>离线验证：定期分析冲突案例，迭代调整权重和规则（比如新增“实体匹配加分”规则），逐步降低冲突率（从15%降到5%）。</li></ol><h1 id="聊天助手Agent"><a href="#聊天助手Agent" class="headerlink" title="聊天助手Agent"></a>聊天助手Agent</h1><p>构建基于 Elasticsearch 的 RAG 检索流⽔线，通过集成 KNN 向量检索与 BM25 关键词匹配实现多路召回， 并引⼊ Cross-Encoder 重排模型以优化语义对⻬精度，在平衡系统响应性能的同时将垂直领域问答准确率提 升25% 以上。</p><p>设计基于 LLM 的⾃适应检索模块，通过指代消解与语义扩展优化⾸轮意图识别；针对重排分数低于阈值的场 景，触发查询重写与回退机制，提升检索召回质量。同时集成⻆⾊与组织标签实现精细化权限管控，确保数据 隔离安全。</p><h4 id="你设计的基于-LLM-的自适应检索模块整体架构是怎样的？核心流程是什么？"><a href="#你设计的基于-LLM-的自适应检索模块整体架构是怎样的？核心流程是什么？" class="headerlink" title="你设计的基于 LLM 的自适应检索模块整体架构是怎样的？核心流程是什么？"></a>你设计的基于 LLM 的自适应检索模块整体架构是怎样的？核心流程是什么？</h4><p><strong>核心回答</strong>：</p><p>整体架构采用「<strong>意图理解层→检索召回层→重排过滤层→自适应优化层→权限管控层</strong>」的分层设计，核心流程如下：</p><ol><li><strong>首轮输入处理</strong>：用户查询进入后，先通过 LLM 完成「指代消解 + 语义扩展」，解决 “他 / 这个功能 / 上周的报表” 等指代模糊问题，同时扩展核心语义（如 “查销售数据” 扩展为 “查询 2024 年 Q2 华东区销售业绩报表 + 销售额 + 环比数据”）；</li><li><strong>基础检索</strong>：将优化后的查询送入向量库 + 关键词检索引擎，获取初始召回结果；</li><li><strong>重排评分</strong>：通过 Cross-BERT 等模型对召回结果重排，输出重排分数；</li><li><strong>自适应决策</strong>：若重排分数≥阈值，直接返回结果 + 权限过滤；若＜阈值，触发「LLM 查询重写」生成 2~3 个改写查询，并行召回后再重排，若仍不达标则触发回退机制（降级为基础关键词检索 + 人工标注结果兜底）；</li><li><strong>权限管控</strong>：全流程嵌入角色 / 组织标签过滤，检索前过滤用户无权限的数据源，检索后过滤结果中敏感数据，最终返回符合权限的结果。</li></ol><p><strong>补充细节</strong>：模块采用 “LLM + 传统检索” 混合架构，既利用 LLM 的语义理解能力，又保留传统检索的高效性，同时通过「规则 + LLM」双校验保证意图识别的稳定性。</p><h4 id="问题-2：指代消解和语义扩展具体是怎么做的？如何落地到首轮意图识别中？"><a href="#问题-2：指代消解和语义扩展具体是怎么做的？如何落地到首轮意图识别中？" class="headerlink" title="问题 2：指代消解和语义扩展具体是怎么做的？如何落地到首轮意图识别中？"></a>问题 2：指代消解和语义扩展具体是怎么做的？如何落地到首轮意图识别中？</h4><p><strong>核心回答</strong>：</p><h5 id="（1）指代消解（解决首轮查询的模糊指代问题）"><a href="#（1）指代消解（解决首轮查询的模糊指代问题）" class="headerlink" title="（1）指代消解（解决首轮查询的模糊指代问题）"></a>（1）指代消解（解决首轮查询的模糊指代问题）</h5><ul><li><p>技术方案：采用「规则兜底 + LLM 精准消解」的混合策略：</p><ul><li><p>规则层：基于依存句法分析（如 HanLP/LTP）识别代词（他 / 它 / 该 / 此）、省略成分，匹配上下文 / 领域词典（如 “报表” 关联 “销售报表 / 财务报表”）；</p></li><li><p>LLM 层：设计专属 prompt，输入 “用户查询 + 对话上下文（若有）+ 领域实体库”，指令 LLM 输出 “消解后的完整查询 + 指代映射关系”，示例 prompt：</p><figure class="highlight armasm"><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">任务：消解查询中的指代成分，输出完整无歧义的查询。</span><br><span class="line">输入：用户查询=“把它发给我”，上下文=“用户此前询问<span class="number">2024</span>年<span class="built_in">Q2</span>销售报表”，领域实体库=[销售报表、财务报表、华东区业绩]</span><br><span class="line">输出要求：完整查询+指代映射（如：完整查询=把<span class="number">2024</span>年<span class="built_in">Q2</span>销售报表发给我；指代映射=它→<span class="number">2024</span>年<span class="built_in">Q2</span>销售报表）</span><br></pre></td></tr></table></figure></li></ul></li><li><p>落地：首轮查询先过规则消解，若消解置信度＜0.8，触发 LLM 消解，确保意图无歧义。</p></li></ul><h5 id="（2）语义扩展（解决首轮查询语义单薄问题）"><a href="#（2）语义扩展（解决首轮查询语义单薄问题）" class="headerlink" title="（2）语义扩展（解决首轮查询语义单薄问题）"></a>（2）语义扩展（解决首轮查询语义单薄问题）</h5><ul><li><p>技术方案：「静态扩展 + LLM 动态扩展」结合：</p><ul><li><p>静态扩展：基于行业词库（如同义词林、上下位词库）扩展核心词（如 “查数据”→“查询 / 统计 / 导出 数据 / 报表 / 明细”）；</p></li><li><p>LLM 动态扩展：输入消解后的查询，指令 LLM 生成 “用户可能想表达的 3 个相关查询”（避免过度扩展），示例：</p><figure class="highlight armasm"><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></pre></td><td class="code"><pre><span class="line">输入：查询=“查询<span class="number">2024</span>年<span class="built_in">Q2</span>华东区销售数据”</span><br><span class="line">输出：</span><br><span class="line"><span class="number">1</span>. 查询<span class="number">2024</span>年<span class="built_in">Q2</span>华东区销售额及环比增长率</span><br><span class="line"><span class="number">2</span>. 导出<span class="number">2024</span>年<span class="built_in">Q2</span>华东区各省份销售明细报表</span><br><span class="line"><span class="number">3</span>. 统计<span class="number">2024</span>年<span class="built_in">Q2</span>华东区销售数据与<span class="built_in">Q1</span>的对比</span><br></pre></td></tr></table></figure></li></ul></li></ul><ul><li>落地：将扩展后的语义融入检索查询向量，提升首轮召回的覆盖率。</li></ul><h4 id="问题-3：重排分数低于阈值时的查询重写和回退机制具体如何设计？"><a href="#问题-3：重排分数低于阈值时的查询重写和回退机制具体如何设计？" class="headerlink" title="问题 3：重排分数低于阈值时的查询重写和回退机制具体如何设计？"></a>问题 3：重排分数低于阈值时的查询重写和回退机制具体如何设计？</h4><p><strong>核心回答</strong>：</p><h5 id="（1）查询重写机制（核心是-“精准改写-多策略备选”）"><a href="#（1）查询重写机制（核心是-“精准改写-多策略备选”）" class="headerlink" title="（1）查询重写机制（核心是 “精准改写 + 多策略备选”）"></a>（1）查询重写机制（核心是 “精准改写 + 多策略备选”）</h5><ul><li><p>触发条件：重排分数＜预设阈值（如 0.7，基于业务场景离线标定）；</p></li><li><p>改写策略：</p><p>① 历史对话融合：LLM 结合多轮对话上下文，补充查询中缺失的上下文信息（如用户仅说 “再查一次”，改写为 “查询 2024 年 Q2 华东区销售数据并导出 Excel”）；</p><p>② 关键词强化：提取原查询核心词，LLM 补充领域限定词（如 “查报表”→“查询 2024 年 Q2 华东区销售业绩报表”）；</p><p>③ 句式转换：将口语化查询转为检索友好型（如 “为啥销售数据低”→“查询 2024 年 Q2 华东区销售数据偏低的原因”）；</p></li><li><p>执行逻辑：LLM 生成 2~3 个改写查询，并行送入检索引擎，取各改写查询召回结果的并集，再统一重排。</p></li></ul><h5 id="（2）回退机制（保证服务兜底能力）"><a href="#（2）回退机制（保证服务兜底能力）" class="headerlink" title="（2）回退机制（保证服务兜底能力）"></a>（2）回退机制（保证服务兜底能力）</h5><ul><li><p>触发条件：查询重写后的重排分数仍＜阈值；</p></li><li><p>回退策略：</p><p>① 降级为 “关键词精准检索”：仅保留原查询核心词（去除扩展词），避免过度泛化；</p><p>② 兜底到人工标注结果：调取该类查询的人工标注高频结果，补充到召回列表；</p><p>③ 反馈提示：返回结果时附带 “是否想查询 XXX？” 的引导式提示，引导用户明确意图。</p></li></ul><h4 id="问题-4：LLM-改写引入的耗时增加，你是怎么优化的？"><a href="#问题-4：LLM-改写引入的耗时增加，你是怎么优化的？" class="headerlink" title="问题 4：LLM 改写引入的耗时增加，你是怎么优化的？"></a>问题 4：LLM 改写引入的耗时增加，你是怎么优化的？</h4><p><strong>核心回答</strong>：</p><p>针对 LLM 改写带来的耗时（单轮 LLM 调用约 200~500ms），从「<strong>链路优化、资源优化、策略优化</strong>」三个维度解决：</p><ol><li><p><strong>链路优化：异步 + 缓存 + 并行</strong></p><ul><li>异步处理：将 LLM 改写与基础检索并行执行（基础检索约 50ms），若基础检索重排分数达标，直接终止 LLM 改写流程；</li><li>缓存策略：对高频查询 / 改写结果做两级缓存（本地缓存 + Redis），缓存 Key 为 “用户查询 + 角色标签”，缓存有效期 1 小时（按业务更新频率调整），命中率可达 60%+，直接跳过 LLM 改写；</li><li>批量改写：将多个低分数查询打包，一次性发送给 LLM，减少调用次数（如每 100ms 批量一次，耗时摊薄至单查询 50ms 内）。</li></ul></li><li><p><strong>资源优化：LLM 轻量化 + 部署优化</strong></p><ul><li>模型选型：首轮意图识别 / 指代消解用轻量开源模型（如 ChatGLM-6B、Qwen-7B），部署为量化版（4bit/8bit），推理耗时从 500ms 降至 150ms；</li><li>部署架构：LLM 服务采用 “主从架构”，主模型处理复杂改写，从模型处理简单指代消解，负载均衡分流。</li></ul></li><li><p><strong>策略优化：动态阈值 + 分级改写</strong></p><ul><li>动态阈值：基于用户场景（如普通用户阈值 0.7，管理员阈值 0.6）、查询类型（如简单查询阈值 0.8，复杂查询阈值 0.6）动态调整，减少不必要的改写触发；</li><li>分级改写：简单查询用规则改写（耗时＜10ms），仅复杂查询触发 LLM 改写，降低 LLM 调用占比。</li></ul></li></ol><p><strong>效果</strong>：优化后整体检索链路耗时从平均 800ms 降至 200ms 内，满足线上服务 P99＜500ms 的要求。</p><h4 id="问题-5：角色与组织标签如何集成到检索模块，实现精细化权限管控和数据隔离？"><a href="#问题-5：角色与组织标签如何集成到检索模块，实现精细化权限管控和数据隔离？" class="headerlink" title="问题 5：角色与组织标签如何集成到检索模块，实现精细化权限管控和数据隔离？"></a>问题 5：角色与组织标签如何集成到检索模块，实现精细化权限管控和数据隔离？</h4><p><strong>核心回答</strong>：</p><p>采用「<strong>标签打标 + 全流程过滤</strong>」的方案，确保 “数据能检索到的前提是用户有权限”，核心流程：</p><ol><li><p><strong>标签体系设计</strong>：</p><ul><li>维度：角色标签（普通用户 / 管理员 / 财务）+ 组织标签（华东区 / 华北区 / 总部）+ 数据标签（公开 / 部门级 / 公司级）；</li><li>打标方式：数据源入库时，通过 LLM + 规则自动打标（如 “华东区销售数据” 打标：组织 = 华东区、数据级别 = 部门级），人工审核兜底。</li></ul></li><li><p><strong>全流程权限过滤</strong>：</p><ul><li>检索前过滤：用户发起查询时，先解析用户的角色 / 组织标签，生成 “权限过滤条件”（如华东区普通用户→仅能检索 “组织 = 华东区 + 数据级别≤部门级” 的数据源），过滤掉无权限的数据源，减少检索范围；</li><li>检索后过滤：对召回结果做二次校验，LLM 识别结果中的敏感数据（如跨组织数据），自动屏蔽 / 脱敏；</li><li>权限缓存：将用户权限标签缓存至本地，避免每次检索都查询权限系统，提升效率。</li></ul></li><li><p><strong>安全兜底</strong>：</p><ul><li>审计日志：记录用户检索的数据源、结果、权限匹配情况，便于追溯；</li><li>熔断机制：若权限服务异常，直接返回 “权限验证失败”，避免数据泄露。</li></ul></li></ol><h4 id="问题-6：自适应检索模块如何支撑多轮对话场景？"><a href="#问题-6：自适应检索模块如何支撑多轮对话场景？" class="headerlink" title="问题 6：自适应检索模块如何支撑多轮对话场景？"></a>问题 6：自适应检索模块如何支撑多轮对话场景？</h4><p><strong>核心回答</strong>：</p><p>多轮对话的核心是 “上下文有效融合 + 意图漂移修正”，模块通过 3 个关键点支撑：</p><ol><li><strong>上下文管理</strong>：维护「用户会话上下文窗口」（最多保留最近 5 轮对话），并通过 LLM 做上下文摘要（避免窗口过长），摘要内容包含 “核心意图 + 已获取信息 + 待解决问题”；</li><li><strong>多轮指代消解</strong>：首轮消解的基础上，每轮对话都将 “当前查询 + 上下文摘要” 送入 LLM，消解跨轮指代（如用户第 3 轮说 “它的环比呢？”，消解为 “2024 年 Q2 华东区销售数据的环比增长率呢？”）；</li><li><strong>意图漂移修正</strong>：若 LLM 检测到用户意图漂移（如从 “查销售数据” 变为 “查成本数据”），则重置检索策略（重新语义扩展 + 检索）；若意图未漂移（仅补充信息），则复用历史检索结果，仅做局部改写。</li></ol><h4 id="问题-7：怎么衡量这个模块的优化效果？有哪些核心指标？"><a href="#问题-7：怎么衡量这个模块的优化效果？有哪些核心指标？" class="headerlink" title="问题 7：怎么衡量这个模块的优化效果？有哪些核心指标？"></a>问题 7：怎么衡量这个模块的优化效果？有哪些核心指标？</h4><p><strong>核心回答</strong>：</p><p>从「效果 + 性能 + 体验」三个维度设计指标，核心指标及优化效果如下：</p><div class="table-container"><table><thead><tr><th style="text-align:center">维度</th><th style="text-align:center">核心指标</th><th style="text-align:center">优化前</th><th style="text-align:center">优化后</th></tr></thead><tbody><tr><td style="text-align:center">效果</td><td style="text-align:center">首轮意图识别准确率</td><td style="text-align:center">75%</td><td style="text-align:center">92%</td></tr><tr><td style="text-align:center"></td><td style="text-align:center">检索召回率（Top20）</td><td style="text-align:center">68%</td><td style="text-align:center">85%</td></tr><tr><td style="text-align:center"></td><td style="text-align:center">重排分数达标率</td><td style="text-align:center">70%</td><td style="text-align:center">88%</td></tr><tr><td style="text-align:center">性能</td><td style="text-align:center">检索链路平均耗时</td><td style="text-align:center">800ms</td><td style="text-align:center">200ms</td></tr><tr><td style="text-align:center"></td><td style="text-align:center">LLM 调用占比</td><td style="text-align:center">100%</td><td style="text-align:center">40%</td></tr><tr><td style="text-align:center">体验</td><td style="text-align:center">用户二次提问率</td><td style="text-align:center">35%</td><td style="text-align:center">12%</td></tr><tr><td style="text-align:center"></td><td style="text-align:center">权限违规访问拦截率</td><td style="text-align:center">90%</td><td style="text-align:center">100%</td></tr></tbody></table></div><p><strong>补充</strong>：通过 A/B 测试验证效果，实验组为 “自适应检索模块”，对照组为 “传统检索”，持续监控指标并迭代阈值 / 改写策略。</p><h3 id="二、核心难点及解决方案"><a href="#二、核心难点及解决方案" class="headerlink" title="二、核心难点及解决方案"></a>二、核心难点及解决方案</h3><div class="table-container"><table><thead><tr><th style="text-align:center">核心难点</th><th style="text-align:center">具体解决方案</th></tr></thead><tbody><tr><td style="text-align:center">1. 首轮意图识别的准确性（指代消解歧义、语义扩展过度 / 不足）</td><td style="text-align:center">① 规则 + LLM 双校验指代消解，设置消解置信度阈值；② 语义扩展限制数量（3 个以内），结合领域词库避免过度扩展；③ 离线标注意图样本，微调 LLM 提升行业适配性</td></tr><tr><td style="text-align:center">2. LLM 改写的耗时与召回质量平衡</td><td style="text-align:center">① 异步并行执行 LLM 改写与基础检索；② 高频查询缓存改写结果；③ 轻量 LLM 模型 + 批量调用降低耗时；④ 动态阈值减少不必要改写</td></tr><tr><td style="text-align:center">3. 多轮对话上下文融合（意图漂移、上下文冗余）</td><td style="text-align:center">① 上下文摘要压缩窗口长度；② LLM 检测意图漂移并重置检索策略；③ 跨轮指代消解结合上下文摘要，保证意图连贯</td></tr><tr><td style="text-align:center">4. 细粒度权限管控与检索性能的平衡</td><td style="text-align:center">① 检索前过滤无权限数据源，减少检索范围；② 缓存用户权限标签，避免重复查询；③ 规则 + 轻量化 LLM 做检索后过滤，替代全量 LLM 校验</td></tr><tr><td style="text-align:center">5. LLM 服务抖动 / 失败对检索链路的稳定性影响</td><td style="text-align:center">① 设计降级策略：LLM 服务异常时，自动切换为 “规则改写 + 传统检索”；② 多 LLM 实例容灾，主实例故障自动切从实例；③ 超时控制（LLM 调用超时 100ms 则终止）</td></tr><tr><td style="text-align:center">6. 查询重写的有效性（改写后召回结果仍不达标的情况）</td><td style="text-align:center">① 离线构建改写模板库，LLM 基于模板生成改写查询；② 对改写结果做 “有效性预校验”（匹配核心词 / 领域词），无效则重新改写；③ 回退机制兜底，保证基础召回能力</td></tr></tbody></table></div><h3 id=""><a href="#" class="headerlink" title=" "></a> </h3><h2 id="统一多源文档格式"><a href="#统一多源文档格式" class="headerlink" title="统一多源文档格式"></a>统一多源文档格式</h2><p>票据,扫描件,手写,文件里的嵌入表格和数学公式</p><p><strong>Word文件</strong></p><p>文档格式.doc,docx,是Word文档的OPen XML格式.</p><p>块的分布 块与块之间的关系</p><p><img data-src="https://s2.loli.net/2026/01/13/OxiwCam8R5HzroU.png" alt="image-20260113144028000" style="zoom:67%;" /> </p><p><strong>文档解析工具</strong></p><p>python-docx,langchain document loader,springlangchain4j,spring ai</p><p><strong>文档解析逻辑</strong></p><p>文字通过库直接解析,图片使用路径表示</p><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">parse_docx</span>(<span class="params">self, docx_path</span>):</span><br><span class="line">       doc = DocxDocument(docx_path)</span><br><span class="line"></span><br><span class="line">       content = []</span><br><span class="line"></span><br><span class="line">       image_map = <span class="variable language_">self</span>._extract_images_from_docx(doc)</span><br><span class="line"></span><br><span class="line">       <span class="keyword">def</span> <span class="title function_">parse_paragraph</span>(<span class="params">paragraph</span>):</span><br><span class="line">           <span class="keyword">def</span> <span class="title function_">append_image_link</span>(<span class="params">image_id, has_drawing, target_buffer</span>):</span><br><span class="line">               <span class="string">&quot;&quot;&quot;Helper to append image link from image_map based on relationship type.&quot;&quot;&quot;</span></span><br><span class="line">               rel = doc.part.rels[image_id]</span><br><span class="line">               <span class="keyword">if</span> rel.is_external:</span><br><span class="line">                   <span class="keyword">if</span> image_id <span class="keyword">in</span> image_map <span class="keyword">and</span> <span class="keyword">not</span> has_drawing:</span><br><span class="line">                       target_buffer.append(image_map[image_id])</span><br><span class="line">               <span class="keyword">else</span>:</span><br><span class="line">                   image_part = rel.target_part</span><br><span class="line">                   <span class="keyword">if</span> image_part <span class="keyword">in</span> image_map <span class="keyword">and</span> <span class="keyword">not</span> has_drawing:</span><br><span class="line">                       target_buffer.append(image_map[image_part])</span><br><span class="line"></span><br><span class="line">           <span class="keyword">def</span> <span class="title function_">process_run</span>(<span class="params">run, target_buffer</span>):</span><br><span class="line">               <span class="comment"># Helper to extract text and embedded images from a run element and append them to target_buffer</span></span><br><span class="line">               <span class="keyword">if</span> <span class="built_in">hasattr</span>(run.element, <span class="string">&quot;tag&quot;</span>) <span class="keyword">and</span> <span class="built_in">isinstance</span>(run.element.tag, <span class="built_in">str</span>) <span class="keyword">and</span> run.element.tag.endswith(<span class="string">&quot;r&quot;</span>):</span><br><span class="line">                   <span class="comment"># Process drawing type images</span></span><br><span class="line">                   drawing_elements = run.element.findall(</span><br><span class="line">                       <span class="string">&quot;.//&#123;http://schemas.openxmlformats.org/wordprocessingml/2006/main&#125;drawing&quot;</span></span><br><span class="line">                   )</span><br><span class="line">                   has_drawing = <span class="literal">False</span></span><br><span class="line">                   <span class="keyword">for</span> drawing <span class="keyword">in</span> drawing_elements:</span><br><span class="line">                       blip_elements = drawing.findall(</span><br><span class="line">                           <span class="string">&quot;.//&#123;http://schemas.openxmlformats.org/drawingml/2006/main&#125;blip&quot;</span></span><br><span class="line">                       )</span><br><span class="line">                       <span class="keyword">for</span> blip <span class="keyword">in</span> blip_elements:</span><br><span class="line">                           embed_id = blip.get(</span><br><span class="line">                               <span class="string">&quot;&#123;http://schemas.openxmlformats.org/officeDocument/2006/relationships&#125;embed&quot;</span></span><br><span class="line">                           )</span><br><span class="line">                           <span class="keyword">if</span> embed_id:</span><br><span class="line">                               rel = doc.part.rels.get(embed_id)</span><br><span class="line">                               <span class="keyword">if</span> rel <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> rel.is_external:</span><br><span class="line">                                   <span class="comment"># External image: use embed_id as key</span></span><br><span class="line">                                   <span class="keyword">if</span> embed_id <span class="keyword">in</span> image_map:</span><br><span class="line">                                       has_drawing = <span class="literal">True</span></span><br><span class="line">                                       target_buffer.append(image_map[embed_id])</span><br><span class="line">                               <span class="keyword">else</span>:</span><br><span class="line">                                   <span class="comment"># Internal image: use target_part as key</span></span><br><span class="line">                                   image_part = doc.part.related_parts.get(embed_id)</span><br><span class="line">                                   <span class="keyword">if</span> image_part <span class="keyword">in</span> image_map:</span><br><span class="line">                                       has_drawing = <span class="literal">True</span></span><br><span class="line">                                       target_buffer.append(image_map[image_part])</span><br><span class="line">                   <span class="comment"># Process pict type images</span></span><br><span class="line">                   shape_elements = run.element.findall(</span><br><span class="line">                       <span class="string">&quot;.//&#123;http://schemas.openxmlformats.org/wordprocessingml/2006/main&#125;pict&quot;</span></span><br><span class="line">                   )</span><br><span class="line">                   <span class="keyword">for</span> shape <span class="keyword">in</span> shape_elements:</span><br><span class="line">                       <span class="comment"># Find image data in VML</span></span><br><span class="line">                       shape_image = shape.find(</span><br><span class="line">                           <span class="string">&quot;.//&#123;http://schemas.openxmlformats.org/wordprocessingml/2006/main&#125;binData&quot;</span></span><br><span class="line">                       )</span><br><span class="line">                       <span class="keyword">if</span> shape_image <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> shape_image.text:</span><br><span class="line">                           image_id = shape_image.get(</span><br><span class="line">                               <span class="string">&quot;&#123;http://schemas.openxmlformats.org/officeDocument/2006/relationships&#125;id&quot;</span></span><br><span class="line">                           )</span><br><span class="line">                           <span class="keyword">if</span> image_id <span class="keyword">and</span> image_id <span class="keyword">in</span> doc.part.rels:</span><br><span class="line">                               append_image_link(image_id, has_drawing, target_buffer)</span><br><span class="line">                       <span class="comment"># Find imagedata element in VML</span></span><br><span class="line">                       image_data = shape.find(<span class="string">&quot;.//&#123;urn:schemas-microsoft-com:vml&#125;imagedata&quot;</span>)</span><br><span class="line">                       <span class="keyword">if</span> image_data <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">                           image_id = image_data.get(<span class="string">&quot;id&quot;</span>) <span class="keyword">or</span> image_data.get(</span><br><span class="line">                               <span class="string">&quot;&#123;http://schemas.openxmlformats.org/officeDocument/2006/relationships&#125;id&quot;</span></span><br><span class="line">                           )</span><br><span class="line">                           <span class="keyword">if</span> image_id <span class="keyword">and</span> image_id <span class="keyword">in</span> doc.part.rels:</span><br><span class="line">                               append_image_link(image_id, has_drawing, target_buffer)</span><br><span class="line">               <span class="keyword">if</span> run.text.strip():</span><br><span class="line">                   target_buffer.append(run.text.strip())</span><br></pre></td></tr></table></figure><p>dify中word提取逻辑,Word 文档（<code>.docx</code>）本质上是一个压缩的 XML 文件。D通过解析这些底层的 XML 标签来重构表格和布局。</p><ol><li>图片转存逻辑</li></ol><ul><li><strong>处理位置</strong>：<code>_extract_images_from_docx(self, doc)</code> 方法。</li><li><strong>具体细节</strong>：<ul><li>它遍历 <code>doc.part.rels</code>（文档关系表）。</li><li>如果是<strong>内部图片</strong>，直接读取 <code>rel.target_part.blob</code>。</li><li>如果是<strong>外部图片 URL</strong>，使用 <code>ssrf_proxy.get(url)</code> 下载（<strong>重点：通过代理下载以防止 SSRF 攻击</strong>）。</li><li><strong>存储与映射</strong>：调用 <code>storage.save()</code> 将图片存入系统，并向数据库 <code>UploadFile</code> 表写入记录。最后返回 <code>image_map</code>，其 Key 是图片在 XML 中的 ID，Value 是 Markdown 格式的链接：<code>![image](预览地址)</code>。</li></ul></li></ul><ol><li>表格转 Markdown 逻辑</li></ol><ul><li><strong>处理位置</strong>：<code>_table_to_markdown(self, table, image_map)</code> 及其调用的 <code>_parse_row</code> 和 <code>_parse_cell</code>。</li><li><strong>具体细节</strong>：<ul><li><strong>对齐处理</strong>：计算表格的最大列数 <code>total_cols</code>。</li><li><strong>格式构造</strong>：手动拼接 Markdown 的分隔符 <code>| --- |</code>。</li><li><strong>单元格合并处理</strong>：在 <code>_parse_row</code> 中，通过 <code>cell.grid_span</code> 获取单元格跨越的列数，确保合并单元格在 Markdown 转换后不会导致列偏移，保持数据结构的对齐。</li></ul></li></ul><ol><li>复杂文本与链接提取</li></ol><ul><li><strong>处理位置</strong>：<code>parse_paragraph</code>（在 <code>parse_docx</code> 内部定义的函数）。</li><li><strong>具体细节</strong>：<ul><li><strong>普通文本</strong>：遍历 <code>run.text</code>。</li><li><strong>现代超链接</strong>：处理 <code>w:hyperlink</code> 标签。</li><li><strong>旧式超链接 (Field)</strong>：这是最难处理的部分，代码通过正则匹配 <code>HYPERLINK &quot;url&quot;</code> 并结合 <code>w:fldChar</code>（字段开始/分隔/结束状态机）来提取那些隐藏在 Word 指令字段中的链接。</li></ul></li></ul><p>直接提取纯文本会丢失表格的行级/列级对应关系。转换为 Markdown 后，大模型能够通过分隔符明确识别表格数据的关联性，减少幻觉。</p><p>实际研发场景中，使用 Document Loader 文档加载器模块时，需要根据具体的业务需求编写自定义的文档后处理逻辑。针对业务需求，开发者可以自行编写和实现对不同文档内容的解析，例如对标题、段落、表格、图片等元素的特殊处理。</p><h3 id="PDF解析"><a href="#PDF解析" class="headerlink" title="PDF解析"></a>PDF解析</h3><p>尽管PDF文件的内容在表达图像、文字和表格信息，但其<strong>本质上是一系列显示和打印指令的集合</strong>。，即使是一个仅包含 “Hello World” 文字的简单PDF，其文件内容也是一长串的打印指令。</p><p><img data-src="https://s2.loli.net/2026/01/13/HQyj94GeUDbJXvL.png" alt="image-20260113151406760" style="zoom:67%;" /></p><p>PDF文件的<strong>显示效果不受设备、软件或系统的影响，但对计算机而言，它是一种非数据结构化的格式，</strong>储存的信息无法直接被理解。此外，大模型的训练数据中不包含直接的PDF文件，无法直接理解。</p><p>PDF解析，对于纯文本格式可以转换为文本字符串，而对于包含多种元素的复杂格式，选择 <strong>MarkDown 文件</strong>作为统一的输出格式最为合适。这是因为MarkDown文件关注内容本身，而非打印格式，能够表示多种文档元素内容。MarkDown格式被广泛接受于互联网世界，其信息能够被大模型理解。</p><p><strong>PDF文件分为电子版和扫描版。</strong>PDF电子版可以<strong>通过规则解析</strong>，提取出文本、表格等文档元素。目前，有许多开源库可以支持，例如 <strong>pyPDF2、PyMuPDF、pdfminer、pdfplumber和papermage</strong> 等。这些库在 langchain_community.document_loaders 中基本都有对应的加载器，方便在不同场景下切换使用。</p><p><strong>在基于规则的开源库中，pdfplumber对中文支持较好，且在表格解析方面表现优秀，但对双栏文本的解析能力较差；pdfminer和PyMuPDF对中文支持良好，但表格解析效果较弱；pyPDF2对英文支持较好，但中文支持较差；papermage集成了pdfminer和其他工具，特别适合处理论文场景。</strong>开发者可以根据实际业务场景的测试结果选择合适的工具，pdfplumber或pdfminer都是当前不错的选择。</p><p><strong>扫描版PDF</strong></p><p>PDF扫描版<strong>需要经过文本识别和表格识别PDF扫描图像</strong>，才能提取出文档中的各类元素。同时要真正实现文档解析的目标，<strong>无论扫描版还是电子版均需进行版面分析和阅读顺序的还原，将内容解析为一个包含所有文档元素并且具有正确阅读顺序的MarkDown文件</strong>。单纯依赖规则解析是无法实现这一目标的.</p><p>目前支持这些功能的多为<strong>基于深度学习的开源库，如 Layout-parser、PP-StructureV2、PDF-Extract-Kit、pix2text、MinerU、marker等</strong>。</p><p>由于PDF文档解析整体流程用到了多个深度学习模型组合，真正在生产场景中会遇到效率问题。<strong>商业闭源库</strong>由于其部署的云端集群可以做并行处理和工程效率优化，所以在精度和效率上都能做到生产中的级别，比如TextIn.com、Doc2x、mathpix、庖丁PDFlux、腾讯云文档识别等，当然商业库会存在成本问题，你可以按需选择。</p><p>PDF内容提取逻辑,参考<a href="https://github.com/langgenius/dify/blob/main/api/core/rag/extractor/pdf_extractor.py">dify</a></p><ol><li>图片格式识别（Magic Bytes）</li></ol><ul><li><strong>实现位置</strong>：类属性 <code>IMAGE_FORMATS</code> 和 <code>_extract_images</code> 内部。</li><li><strong>逻辑</strong>：代码通过检查文件头的“魔数”（Magic Bytes）来精准识别图片格式（如 JPEG 的 <code>\xff\xd8\xff</code>，PNG 的 <code>\x89PNG</code>），而不是简单依赖文件后缀。这保证了即使 PDF 内部图像流没有明确标签，也能正确保存为 <code>.jpg</code> 或 <code>.png</code>。</li></ul><ol><li>图像转存与持久化</li></ol><ul><li><strong>处理位置</strong>：<code>_extract_images(self, page)</code>。</li><li><strong>具体细节</strong>：<ul><li><strong>对象过滤</strong>：使用 <code>filter=(pdfium_c.FPDF_PAGEOBJ_IMAGE,)</code> 仅提取页面中的图像对象。</li><li><strong>二进制提取</strong>：调用 <code>obj.extract()</code>。注意其中的 <code>fb_format=&quot;png&quot;</code>，这意味着对于非 JPEG 格式的图像，代码会自动将其回退（Fallback）并渲染为 PNG 格式以保证兼容性。</li><li><strong>存储与数据库</strong>：图片字节流被存入 <code>storage</code>，同时向 <code>UploadFile</code> 数据库表写入元数据。返回的 Markdown 链接允许前端或大模型直接通过 URL 预览提取到的图片。</li></ul></li></ul><ol><li>内存与资源管理</li></ol><ul><li><strong>处理位置</strong>：<code>parse</code> 方法中的 <code>finally</code> 块及 <code>autoclose=True</code>。</li><li><strong>逻辑</strong>：代码显式调用 <code>text_page.close()</code>、<code>page.close()</code> 和 <code>pdf_reader.close()</code>。在处理包含数千张图片的 PDF 时，及时释放 C 级底层的 PDF 句柄是防止内存泄漏的关键。</li></ul><h4 id="优化解析逻辑"><a href="#优化解析逻辑" class="headerlink" title="优化解析逻辑"></a>优化解析逻辑</h4><p><strong>布局恢复</strong>：目前提取的是“平铺”文本，PDF 中的<strong>多栏布局或页眉页脚</strong>可能会混入正文。可以考虑引入 <code>pypdfium2</code> 的坐标分析功能来过滤页眉页脚,或者使用OCR提取布局信息。</p><p><strong>表格识别</strong>: pdf底层存储的是字符及其坐标,需要通过定位页面中的表格区域。尝试理解表格的行列逻辑，并将其转换为标准的 <strong>Markdown 表格格式</strong>（<code>|---|</code>）。结合布局分析和表格重建 <a href="https://github.com/infiniflow/ragflow">infiniflow/ragflow</a></p><p><strong>OCR 介入</strong>：如果 PDF 是扫描件（图片生成的 PDF），<code>get_text_range()</code> 将返回空。此时应触发 OCR 流程对提取出的图片进行二次文字识别。</p><p><strong>表格识别</strong>:引入OCR</p><p>采取 <strong>“混合解析策略”</strong>：</p><ol><li><strong>预判断</strong>：先尝试用 <code>pypdfium2</code> 读取文本，如果返回字符数极少，判定为扫描件，触发 OCR。</li><li><strong>工具选型</strong>：<ul><li><strong>追求开源/本地部署</strong>：集成 <code>Marker</code> 或 <code>Unstructured</code> 库。</li><li><strong>追求极致效果</strong>：使用多模态 API（如 GPT-4o-mini）专门处理表格页。</li></ul></li><li><strong>标准化输出</strong>：无论后端用什么工具，统一输出为 <strong>Markdown</strong>，因为这能最大程度保留表格的语义逻辑供大模型检索。</li></ol><p><strong>推荐工具对比：</strong></p><div class="table-container"><table><thead><tr><th><strong>需求场景</strong></th><th><strong>推荐工具</strong></th><th><strong>集成难度</strong></th></tr></thead><tbody><tr><td><strong>追求极致速度</strong></td><td>维持现状 (<code>pypdfium2</code>)</td><td>-</td></tr><tr><td><strong>精准处理多栏/论文</strong></td><td>集成 <code>Marker</code></td><td>中（需要额外 Python 依赖）</td></tr><tr><td><strong>完美还原复杂表格</strong></td><td><code>Unstructured</code> + <code>PaddleOCR</code></td><td>高（环境配置较复杂）</td></tr><tr><td><strong>万能适配</strong></td><td>多模态 LLM 视觉解析</td><td>低（仅需 API 调用）</td></tr></tbody></table></div><p>在Marker中,使用深度学习模型和启发式算法来解析PDF中的多栏布局和表格。多栏文本通过<code>TextProcessor</code>检测和合并，表格通过<code>TableProcessor</code>进行结构化提取，并可选择性使用LLM增强准确性</p><p>开源系统不支持的文件类型</p><p><img data-src="https://s2.loli.net/2026/01/13/LJ5gIYocRW1OMhs.png" alt="image-20260113162924676" style="zoom:50%;" /></p><p><img data-src="https://s2.loli.net/2026/01/13/a93OWviDeEuVHtS.png" alt="image-20260113164413896" style="zoom:50%;" /></p><h3 id="难点"><a href="#难点" class="headerlink" title="难点"></a>难点</h3><h4 id="跨页表格怎么自动对齐"><a href="#跨页表格怎么自动对齐" class="headerlink" title="跨页表格怎么自动对齐"></a>跨页表格怎么自动对齐</h4><p>在处理 PDF 解析时，<strong>跨页表格（Multi-page Tables）</strong>是最具挑战性的场景之一。由于 PDF 在分页处会强制打断表格结构，并可能插入页眉、页脚或重复表头，简单的流式提取会导致表格断裂或数据错位。</p><p><strong>关键技术方案</strong></p><p>表头一致性,判断是否为同一张表的重复表头</p><p>表格位置分析,判断相邻页中的表格是否在文档布局中连续</p><p>自动拼接与去重,对于相同表头的多个分页表格,按行拼接,并去除重复的表头行</p><ol><li>布局检测</li></ol><p>mineru</p><ol><li>文档格式检测</li><li>文档格式识别</li><li>OCR处理</li><li>表格预测</li></ol><h2 id="数据预处理"><a href="#数据预处理" class="headerlink" title="数据预处理"></a>数据预处理</h2><p><img data-src="https://s2.loli.net/2026/01/13/HtOu2piYNzmc5oT.png" alt="image-20260113172147249"></p><p>构建领域术语词库,识别输入中领域术语,替换</p><p>术语混淆直接影响信息检索的精确度与生成内容质量.</p><h3 id="术语词库构建与维护"><a href="#术语词库构建与维护" class="headerlink" title="术语词库构建与维护"></a>术语词库构建与维护</h3><p><strong>产生术语混淆</strong></p><p>术语多义性,同义词,领域差异以及企业专属术语</p><p><strong>术语词库构建流程</strong></p><ol><li>收集术语来源</li><li>标准化术语</li><li>建立别名映射关系</li><li>添加上下文信息</li><li>构建术语索引</li></ol><p><img data-src="https://s2.loli.net/2026/01/13/jnPqIHEdLuV2g8l.png" alt="image-20260113192853262"></p><p><strong>术语词库与RAG集成</strong></p><p>方式1:预处理阶段替换术语</p><p>方式2:检索增强</p><p>方式3:重排序</p><p>方式4:后处理解释</p><p><strong>维护术语词库</strong></p><h2 id="分块策略与Embedding技术"><a href="#分块策略与Embedding技术" class="headerlink" title="分块策略与Embedding技术"></a>分块策略与Embedding技术</h2><p><img data-src="https://s2.loli.net/2026/01/13/FmMrCP7X6E3NsRV.png" alt="image-20260113172431541"></p><p><strong>数据分块（Chunking/切片）</strong> 的质量直接决定了检索的精准度。如果<strong>切片太小，会丢失上下文</strong>；如果<strong>切片太大，会引入噪声并稀释语义。</strong></p><p>Token-based chunking TokenTextSplitter 严格限制模型窗口时物理边界控制精准</p><p>SentenceSplitter 通用文本文档语义保真度高，配置简单 目标是在保持语义完整性的前提下，尽可能按句子边界切分。</p><p><strong>工作原理</strong>：它会尝试按照段落 (<code>\n\n</code>)、换行 (<code>\n</code>)、句子、单词的优先级递归切分。</p><p><strong>核心作用</strong>：它能确保切片<strong>不会在句子中间断开</strong>。</p><p><strong>关键参数</strong>：<code>chunk_size</code>（块大小）和 <code>chunk_overlap</code>（重叠大小）。</p><p> 窗口切分, SentenceWindowNodeParser </p><div class="table-container"><table><thead><tr><th><strong>文档类型</strong></th><th><strong>推荐切片类 (LlamaIndex)</strong></th><th><strong>选择理由</strong></th></tr></thead><tbody><tr><td><strong>标准说明书/公文</strong></td><td><code>SentenceSplitter</code></td><td>段落结构清晰，按句子切分能最大程度保留语义。</td></tr><tr><td><strong>README/技术手册</strong></td><td><code>MarkdownNodeParser</code></td><td><strong>强推。</strong> 必须按 <code>H1-H3</code> 标题切分，否则正文会脱离标题，导致检索到正文却不知道在说哪个产品。</td></tr><tr><td><strong>代码库 (Python/JS)</strong></td><td><code>CodeSplitter</code></td><td>依据语法树（AST）切分，保证一个函数或一个类在一个 Chunk 里。</td></tr><tr><td><strong>扫描件/复杂 PDF</strong></td><td><code>SemanticSplitter</code></td><td><strong>慎用。</strong> 建议先用 <code>Marker</code> 转为 Markdown，再用 <code>MarkdownNodeParser</code> 处理。</td></tr></tbody></table></div><p><strong>“全家桶”组合</strong> 通常是：</p><ol><li><strong>MarkdownNodeParser</strong>（用于格式化内容）</li><li><ul><li><strong>SentenceSplitter</strong>（作为 fallback 处理长段落）</li></ul></li><li><ul><li><strong>MetadataExtractor</strong>（注入标题和上下文）</li></ul></li><li><ul><li><strong>RedisCache</strong>（放在 IngestionPipeline 里加速重复解析</li></ul></li></ol><p>滑动窗口切分=sentenceSplitter+SentenceWindowSplitter</p><h4 id="滑动窗口"><a href="#滑动窗口" class="headerlink" title="滑动窗口"></a>滑动窗口</h4><p>滑动窗口结合关键词</p><ol><li>初步切片,使用滑动窗口对文档进行基础分段</li><li>关键词检测,分析每个切片的边界,识别是否存在关键词或语义单元的中断</li><li>动态调整,根据关键词位置和上下文语境,微调切片边界,确保语义完整</li></ol><p><strong>语义切分</strong></p><p><img data-src="https://s2.loli.net/2026/01/13/uWSVRKQ13fZlOde.png" alt="image-20260113214337874"></p><p>SemanticSplitter 的工作逻辑可以分为以下四个步骤：</p><ol><li><strong>句子分割</strong>：首先将原始文档拆分成一个个基础的句子。</li><li><strong>向量化（Embedding）</strong>：利用 Embedding 模型将每个句子（或句组）转化为高维向量。</li><li><strong>计算相似度</strong>：计算相邻句子之间的<strong>余弦相似度（Cosine Similarity）</strong>。</li><li><strong>设定阈值（Threshold）</strong>：<ul><li>如果<strong>相邻两句的意思非常接近，它们会被合并在同一个块（Chunk）里</strong>。</li><li>如果相似度突然下降，超过了设定的阈值，系统就认为“话题发生了转换”，并在此处建立切片断点。</li></ul></li></ol><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> llama_index.core.node_parser <span class="keyword">import</span> SemanticSplitterNodeParser</span><br><span class="line"><span class="keyword">from</span> llama_index.embeddings.openai <span class="keyword">import</span> OpenAIEmbedding</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 定义 Embedding 模型（用于计算句子间的相似度）</span></span><br><span class="line">embed_model = OpenAIEmbedding()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 初始化语义切片器</span></span><br><span class="line"><span class="comment"># buffer_size: 计算相似度时考虑的窗口大小</span></span><br><span class="line"><span class="comment"># breakpoint_percentile_threshold: 相似度下降到多少百分位时进行切割</span></span><br><span class="line">splitter = SemanticSplitterNodeParser(</span><br><span class="line">    buffer_size=<span class="number">1</span>, </span><br><span class="line">    breakpoint_percentile_threshold=<span class="number">95</span>, </span><br><span class="line">    embed_model=embed_model</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 对文档进行切片</span></span><br><span class="line">nodes = splitter.get_nodes_from_documents(documents)</span><br></pre></td></tr></table></figure><h2 id="动态切片策略与重叠机制"><a href="#动态切片策略与重叠机制" class="headerlink" title="动态切片策略与重叠机制"></a>动态切片策略与重叠机制</h2><p><strong>RecursiveTextSplitter</strong>（递归字符切片器）是 RAG 开发中最常用、也是性价比最高的文本切片工具。它被广泛集成在 LangChain 等框架中，旨在解决简单字符切片容易“切断语义”的问题。</p><p>它的核心理念是：<strong>尽可能保持段落、句子和词语的完整性。</strong></p><p><code>RecursiveTextSplitter</code> 维护了一个<strong>分隔符列表</strong>（默认通常是 <code>[&quot;\n\n&quot;, &quot;\n&quot;, &quot; &quot;, &quot;&quot;]</code>）。其工作流程如下：</p><ol><li><strong>段落切分</strong>：首先尝试用双换行符 <code>\n\n</code>（段落边界）来切分文档。</li><li><strong>检查大小</strong>：如果切出来的块小于设定的 <code>chunk_size</code>，则保留。</li><li><strong>降级递归</strong>：如果某个块仍然超过了 <code>chunk_size</code>，它会针对这个超长的块，换用下一个分隔符（如单换行符 <code>\n</code>）继续尝试切分。</li><li><strong>最小单位</strong>：如果连换行符切完都太大，它会寻找空格 <code>&quot; &quot;</code>（单词边界）。如果连空格都解决不了（比如一个极长的化学单词），最后才会按字符 <code>&quot;&quot;</code> 强制切断。</li></ol><p><strong>结构化文档失效</strong>：如果你处理的是 Markdown（带有很多 <code>#</code> 标题）或代码，普通的递归切片可能会切断标题和正文的联系。此时应该使用专用的 <code>MarkdownHeaderTextSplitter</code> 或 <code>CodeSplitter</code>。</p><p><strong>不识别语义转折</strong>：它只看物理符号（如换行符），不看意思。如果一个段落里讨论了两个完全不同的主题，它还是会把它们放在一起。这种情况需要 <code>SemanticSplitter</code>。</p><p><strong>TopicNodeParser</strong></p><p>命题化检索</p><p><code>TopicNodeParser</code> 通常利用 LLM 或高精度的 Embedding 模型来识别文档中的“话题转换点”（Topic Drift）。</p><p>其工作流程通常如下：</p><ol><li><strong>初始分割</strong>：将文档初步拆分为较小的单元（如句子或短段落）。</li><li><strong>主题建模/识别</strong>：<ul><li><strong>方法 A（LLM 驱动）</strong>：让 LLM 扫描文本块，判断：“这里是否开启了一个新话题？”</li><li><strong>方法 B（语义聚类）</strong>：计算连续块之间的相似度，当相似度曲线出现“断崖式”下跌时，判定为主题切换。</li></ul></li><li><strong>节点构建</strong>：将属于同一个主题的所有文本块合并为一个 <code>Node</code>，并自动为该 Node 提取一个<strong>主题标签（Topic Label）</strong>作为元数据。</li></ol><p><strong>上下文割裂</strong>：一个关于“安装步骤”的描述可能被切成了两半，检索时只搜到后半部分，导致步骤缺失。</p><p><strong>语义混杂</strong>：一个 1024 Token 的块里，前一半在说“硬件规格”，后一半在说“软件配置”，这会导致该块的向量表征变得“平庸”，检索相关度下降。</p><p><img data-src="https://s2.loli.net/2026/01/13/oMWHlgAt4XJy5VC.png" alt="image-20260113221722324"></p><h4 id="Dify中的父子模式"><a href="#Dify中的父子模式" class="headerlink" title="Dify中的父子模式"></a>Dify中的父子模式</h4><p>在普通的切片模式下，开发者常面临一个<strong>“鱼与熊掌不可兼得”</strong>的困局：</p><ul><li><strong>分段太小</strong>：检索非常精准（向量表征集中），但由于上下文（Context）太少，LLM 回答时容易断章取义。</li><li><strong>分段太大</strong>：包含了足够的背景，但一个段落里讲了三个主题，导致向量表征模糊，搜索时很难排在前面。</li></ul><p><strong>父子模式的对策</strong>：将原本的大段落（父）进一步拆解为多个小片段（子）。</p><ol><li><p>工作原理</p></li><li><p><strong>存储阶段</strong>：</p><ul><li><strong>父分段 (Parent)</strong>：通常是较长的文本块（如 1000 Tokens），保留了完整的逻辑和上下文。</li><li><strong>子分段 (Child)</strong>：将父分段进一步切碎（如 200 Tokens）。</li><li><strong>数据库关联</strong>：系统在向量数据库中索引“子分段”，但每一个子分段都关联着其对应的“父分段” ID。</li></ul></li><li><strong>检索阶段</strong>：<ul><li>用户提问后，系统去匹配<strong>最相关的子分段</strong>。</li></ul></li><li><strong>还原阶段（关键步）</strong>：<ul><li>一旦命中了某个子分段，Dify 不会将子分段的内容发给 LLM，而是自动根据关联 ID，<strong>找回它所属的父分段内容</strong>。</li></ul></li><li><strong>生成阶段</strong>：<ul><li>LLM 接收到的是语义丰富的父分段，从而能根据完整的背景生成高质量答案</li></ul></li></ol><p>开启“父子模式”通常需要调整以下参数：</p><ul><li><strong>父分段规则</strong>：设定父块的大小（建议 800-1000 字符）。</li><li><strong>子分段规则</strong>：设定子块的大小（建议 200-300 字符）。</li><li><strong>检索策略</strong>：<ul><li>通常配合<strong>混合检索（Hybrid Search）</strong>使用，即：子分段的向量检索 + 全文检索。</li></ul></li></ul><p><strong>适用文档</strong>：逻辑严密但篇幅较长的文档，如<strong>法律条文、技术规范、深度行业报告</strong>。</p><p><strong>配合 Rerank</strong>：在父子模式下，强烈建议开启 <strong>Rerank（重排序）</strong>。因为子块召回可能较多，通过 Rerank 选出最准确的子块对应的父块，能显著降低 Token 消耗并提升准确度。</p><p><img data-src="https://s2.loli.net/2026/01/13/9QqxH8JkfObdPpe.png" alt="image-20260113222328479"></p><p>传统的 RAG 工具（如 LangChain 或 Dify 默认模式）往往采用“一刀切”的规则来切片，而 RAGFlow 的模板化逻辑是：<strong>先看文档长什么样，再决定怎么拆。</strong></p><p>不同的文档有完全不同的逻辑结构。如果用同一种算法去切，会发生以下惨剧：</p><ul><li><strong>财务报表</strong>：表格被拆散成一堆毫无意义的数字。</li><li><strong>科研论文</strong>：双栏排版的文字被横向读取，左右两栏混在一起。</li><li><strong>调查问卷</strong>：问题和选项被强行切断。</li></ul><p><strong>RAGFlow 的模板化方案</strong>：预设了多种针对特定场景的解析逻辑，确保数据从 PDF/Word 转化为数据库向量时，逻辑依然是完整的。</p><p>RAGFlow 提供了多种内置模板，每种模板背后都有一套专门的布局识别（Layout Analysis）算法</p><p>切片的核心作用:为后续混合检索,重排,查询转换等提供高质量数据</p><p>最后也需要构建评估体系,持续验证与迭代优化</p><h2 id="检索增强阶段"><a href="#检索增强阶段" class="headerlink" title="检索增强阶段"></a>检索增强阶段</h2><p>在初步召回的基础上,进一步优化检索结果的广度与精度.</p><p><img data-src="https://s2.loli.net/2026/01/13/3zsZUHCwk5QOaGl.png" alt="image-20260113174601655"></p><p><strong>查询扩展与重写</strong></p><p>生成 3 到 5 个不同表述但语义相同的查询变体。这些变体应从不同的侧重点出发，帮助从向量数据库中找回最相关的文档。其核心依据是<strong>语义向量空间的不确定性</strong>。</p><p>利用大语言模型（LLM）将用户的一个问题转化为多个语义相似但措辞不同的问题，然后分别进行检索。</p><p>传统的向量检索（Vector Search）高度依赖用户输入的<strong>措辞</strong>。</p><ul><li><strong>痛点</strong>：如果用户的问题和文档中的词汇不匹配，或者提问比较模糊，向量空间中的距离可能较远，导致检索不到核心内容。</li><li><strong>例子</strong>：用户问“如何对齐 PDF 表格？”，文档里写的可能是“PDF 表格解析与重组方案”。两者字面上不完全一致。</li></ul><ol><li><strong>查询生成</strong>：LLM 接收原始问题，生成 3-5 个不同角度的变体（例如：“PDF 表格错位怎么处理？”、“PDF 跨页表格对齐技术”等）。</li><li><strong>并行检索</strong>：将这多个问题同时发给向量数据库，每个问题都会召回一批文档。</li><li><strong>取并集（Union）</strong>：将所有问题召回的文档汇总在一起。</li><li><strong>去重与排序</strong>：剔除重复文档，最后将这组更全面的文档交给 LLM 生成答<strong>案</strong></li></ol><p><strong>假设性文档HyDE</strong></p><p>核心思想是与其直接用“问题”去搜答案，不如先让 AI 编一个“假答案”，然后用这个“假答案”去搜“真文档”。</p><p>在传统的向量检索中，我们计算的是 <strong>问题（Query）</strong> 和 <strong>文档（Document）</strong> 之间的相似度。</p><ul><li><strong>痛点</strong>：问题通常很短（如“HyDE 是什么？”），而文档通常很长且充满细节。在向量空间里，短问题和长文档的特征向量往往<strong>不对称</strong>，导致检索不到最相关的片段。</li><li><strong>HyDE 的逻辑</strong>：文档和文档之间是最像的。</li></ul><ol><li><strong>生成假设文档</strong>：接收用户的问题，让 LLM（如 GPT-4）在不看任何外部资料的情况下，凭直觉写一篇“伪文档”或“假设性回答”。</li><li><strong>向量化（Embedding）</strong>：将这篇逻辑通顺但内容可能不准确的<strong>假设文档</strong>转化成向量。</li><li><strong>检索真文档</strong>：用这个“伪向量”去数据库里匹配物理特征最接近的<strong>真实文档</strong>。</li><li><strong>生成最终答案</strong>：把检索到的真文档喂给 AI，生成正式的回复。</li></ol><h2 id="OCR文件错漏"><a href="#OCR文件错漏" class="headerlink" title="OCR文件错漏"></a>OCR文件错漏</h2><p>OCR识别错误可以分为:</p><p>字符识别错误,文字遗漏,多字重复,格式混乱</p><p>在OCR之前可以及逆行文本图像校正,纠正图像中文档扭曲,倾斜,透视变形等问题,另外进行版面区域检测,对文档图像进行内容解析和区域划分</p><p>识别后的内容可以通过LLM修正</p><h2 id="生成控制与验证阶段"><a href="#生成控制与验证阶段" class="headerlink" title="生成控制与验证阶段"></a>生成控制与验证阶段</h2><p><img data-src="https://s2.loli.net/2026/01/13/1MGJDKWENkfjeS4.png" alt="image-20260113191853638"></p><h2 id="向量检索引擎"><a href="#向量检索引擎" class="headerlink" title="向量检索引擎"></a>向量检索引擎</h2><p><img data-src="https://s2.loli.net/2026/01/13/GyxSOqhop9erDBA.png" alt="image-20260113233157267"></p><p>BM25是改进的TF-IDF排序,通过词频,文档长度归一化以及逆文档频率加权.</p><h3 id="设计混合检索架构提高多阶段召回率"><a href="#设计混合检索架构提高多阶段召回率" class="headerlink" title="设计混合检索架构提高多阶段召回率"></a>设计混合检索架构提高多阶段召回率</h3><h2 id="让AI返回结构化数据"><a href="#让AI返回结构化数据" class="headerlink" title="让AI返回结构化数据"></a>让AI返回结构化数据</h2><p>Structured Generation with LLM，是指<strong>让LLM按照预先定义的schema，输出符合schema的结构化结果</strong>。</p><p>常见的应用场景有：</p><ol><li><strong>数据处理</strong>。主要功能为a -&gt; b，即从源文本中<strong>抽取/</strong>生成符合schema的结果，例如给定新闻，进行分类、抽取关键词、生成总结等；</li><li><strong>Agent</strong>。主要功能是Tool Calling，即根据用户query，选择适当的tool和入参</li></ol><p>如何从模型输出中准确提取自己所需的信息。例如，当我们希望模型输出 JSON 格式的数据时，由于模型生成的内容并不总是稳定，可能需要额外编写大量的正则表达式来匹配并提取其中的有效信息。然而，由于 LLM 的能力，导致其输出结构并不永远可靠。</p><p>现阶段， 让LLM按要求生成特定格式文本的主要方法有几种种：</p><ul><li>微调：使模型的输出遵循特定格式</li><li><strong>OpenAI Json-mode/function-calling/Structured Outputs:</strong> 这些功能允许模型生成更严格、结构化的输出，但受限于openAI平台。</li><li><strong>格式约束</strong>：在decoding阶段进行约束，限制模型的输出，</li><li><strong>Prompt Engineering</strong>： 最简单的办法，但不稳定。</li><li>多阶段prompting： 通过多个步骤的提示逐步引导模型生成所需的格式</li></ul><ol><li>提示词工程（Prompt Engineering）</li></ol><p>这是最基础的方式，通过在 System Prompt 中明确要求 AI 返回特定的格式。</p><ul><li><strong>实现方式</strong>：在提示词末尾加上 <code>“请以 JSON 格式返回结果，不要包含任何多余的解释文字”</code>，并给出示例。</li><li><strong>优点</strong>：<ul><li><strong>低成本</strong>：不需要额外的 API 参数或编程逻辑。</li><li><strong>灵活性高</strong>：可以随时调整字段定义。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>不稳定</strong>：AI 可能会在 JSON 前后加上“好的，这是你要的结果”等废话（俗称 Chatty AI）。</li><li><strong>解析失败</strong>：长文本下可能会出现 JSON 语法错误（如少个括号、引号未转义）。</li></ul></li></ul><p><strong>Kor技术</strong></p><p>使用<a href="https://eyurtsev.github.io/kor/">Kor </a>进行structured generation的流程如下：</p><ol><li>定义schema，包括结构、注释还有例子；</li><li>Kor<strong>用特定的<em>prompt template</em>，将用户提供的schema和待处理的raw text</strong>，组装成prompt；</li><li>将prompt发送给LLM，借助其通用的In Context Learning能力，尽量生成符合schema的内容；</li><li><p>Kor对LLM的输出进行parse，返回符合schema的结构化结果，但也有概率没有返回（当LLM的输出不符合schema时）。</p></li><li><p>JSON Mode（JSON 模式）</p></li></ol><p>主流模型（如 GPT-4、Gemini 1.5、Claude 3）都提供了专门的开关。</p><ul><li><strong>实现方式</strong>：在调用 API 时设置 <code>response_format: { &quot;type&quot;: &quot;json_object&quot; }</code>。</li><li><strong>优点</strong>：<ul><li><strong>格式强制</strong>：模型会确保输出是一个合法的 JSON，极大地减少了解析错误。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>不保证内容 Schema</strong>：虽然它是 JSON，但字段名可能会随机变化（比如这次叫 <code>name</code>，下次叫 <code>user_name</code>）。</li><li><strong>仍需引导</strong>：通常还是需要在 Prompt 中提示它是 JSON，否则模型可能不知道该写什么。</li></ul></li></ul><p>仅特定模型和平台支持,需要在prompt中要求输出json格式,不能保证完全按要求的格式结构输出,Json-Mode 更多是对于输出json的格式进行检查.JSON Mode 的实现原理是受限解码,核心不是让模型“学会”写 JSON，而是在它每产生一个 Token 时，通过一个<strong>外部过滤器（Filter）</strong>强行剔除掉会导致语法错误的选项。</p><ol><li>函数调用 / 工具调用（Function Calling / Tool Use）</li></ol><p>这是目前<strong>最推荐</strong>、生产环境最常用的方式。</p><ul><li><strong>实现方式</strong>：预先定义一个函数的参数结构（通常使用 JSON Schema），AI 不直接回答问题，而是通过“调用函数”来填充这些参数。</li><li><strong>优点</strong>：<ul><li><strong>强类型约束</strong>：严格遵循你定义的字段名和数据类型。</li><li><strong>逻辑解耦</strong>：AI 负责提取信息，你的代码负责处理提取后的数据。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>延迟略高</strong>：模型需要额外的思考步骤来决定调用哪个函数。</li><li><strong>成本</strong>：复杂的函数定义会占用更多的输入 Token。</li></ul></li></ul><p>Function Calling是构建agent的基石，也是各大LLM厂商的标配功能。要做到好的FC，LLM要能做到：</p><ol><li>理解任务与function/tool的关系，知道是否要调用、需调用哪些function/tool、是否缺必要参数；</li><li>返回结构化内容，包括function name、arguments（json格式）</li></ol><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>普通 Prompt</strong></th><th><strong>JSON Mode</strong></th><th><strong>Function Calling</strong></th></tr></thead><tbody><tr><td><strong>约束目标</strong></td><td>语义理解（靠 AI 配合）</td><td>语法合规（保证是 JSON）</td><td><strong>Schema 合规（保证字段对齐）</strong></td></tr><tr><td><strong>字段准确度</strong></td><td>差，经常乱起名</td><td>中，字段可能多写或漏写</td><td><strong>极高，严格遵循定义</strong></td></tr><tr><td><strong>复杂结构</strong></td><td>难以处理深层嵌套</td><td>较好</td><td><strong>完美，支持复杂递归嵌套</strong></td></tr><tr><td><strong>稳定性</strong></td><td>易受 Prompt 干扰</td><td>较稳</td><td><strong>工业级稳定</strong></td></tr></tbody></table></div><p>Mistral模型的开源FC实现</p><p>mistral-nemo这样实现FC：</p><ol><li>将tools按照特定的template，组装到prompt中去；</li><li>LLM输出时，也遵循特定的template，call tool时加入特殊标记（TOOL_CALLS），并返回name和arguments。</li></ol><p>mistral-nemo在fine-tuning时，按照这样的格式进行训练，FC的“要求”已经被encode到模型的参数中去了；</p><ol><li>结构化输出（Structured Outputs）</li></ol><blockquote><p>很多开发者会用 <strong>Function Calling 来实现结构化输出</strong>（即使他们并不打算真的调用函数）。</p><ul><li><strong>原因</strong>：早期的模型不支持独立的 Structured Output 模式，但 Function Calling 训练得非常成熟，能极稳地吐出 JSON 参数包。</li><li><strong>现状</strong>：现在像 OpenAI 的 <strong>Structured Outputs</strong> 已经是这两者的结合体——它既可以用在普通的回复里，也可以用在函数调用的参数提取里，保证 100% 的字段匹配。</li></ul></blockquote><p>这是 OpenAI 等厂商近期推出的最高级别约束方案。</p><ul><li><strong>实现方式</strong>：在 API 请求中提供具体的 <code>json_schema</code>，并开启 <code>strict: true</code> 模式。</li><li><strong>优点</strong>：<ul><li><strong>100% 可靠</strong>：通过受限解码技术（Constrained Decoding），模型输出的每一个字符都受到 Schema 约束，完全不会偏离。</li><li><strong>零冗余</strong>：不会有任何解释性文字。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>模型限制</strong>：目前仅支持部分最新型号（如 GPT-4o 系列）。</li><li><strong>Schema 校验严格</strong>：如果 Schema 写的有问题，请求会直接失败。</li></ul></li></ul><ol><li>编程框架封装（Pydantic / LangChain / Instructor）</li></ol><p>利用开源库在代码层面进行封装和后处理。</p><ul><li><strong>实现方式</strong>：使用 Python 的 <code>Pydantic</code> 定义类，通过 <code>Instructor</code> 库调用 AI，自动完成“请求 -&gt; 提取 -&gt; 验证 -&gt; 重试”的闭环。</li><li><strong>优点</strong>：<ul><li><strong>开发体验极佳</strong>：像调用普通函数一样获取 AI 结果。</li><li><strong>自动重试</strong>：如果 AI 第一次返回的 JSON 缺字段，框架会自动反馈错误并让 AI 重写。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>依赖性</strong>：增加了项目的库依赖。</li></ul></li></ul><div class="table-container"><table><thead><tr><th><strong>需求场景</strong></th><th><strong>推荐方式</strong></th><th><strong>稳定等级</strong></th></tr></thead><tbody><tr><td><strong>快速原型、简单测试</strong></td><td>提示词工程</td><td>⭐⭐</td></tr><tr><td><strong>需要合法 JSON 但字段多变</strong></td><td>JSON Mode</td><td>⭐⭐⭐</td></tr><tr><td><strong>生产环境、复杂业务逻辑</strong></td><td><strong>Function Calling</strong></td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>高严苛金融/政务系统</strong></td><td><strong>Structured Outputs (Strict)</strong></td><td>⭐⭐⭐⭐⭐</td></tr></tbody></table></div><h3 id="开源实现"><a href="#开源实现" class="headerlink" title="开源实现"></a>开源实现</h3><p><strong>Outlines</strong></p><p><strong>对于json schema，outlines首先将其转为正则表达式，然后再转为token-level的Finite State Machine</strong></p><p>随后，模型的生成过程就变成在state之间的跳转：首先从初始state出发，随后在有限的输出路径中选一条，到达下一个state，直到到达最后一个state，完成生成。</p><p>其中”有限的输出路径“就是前文所提到的tokens输出范围。</p><p><code>Outlines</code> 的精髓在于它不直接处理 JSON，而是处理 <strong>FSM（有限状态机）</strong>。</p><ol><li><strong>解析 Schema</strong>：当你传入一个 Pydantic 模型或正则表达式时，<code>Outlines</code> 会解析其结构。</li><li><strong>构建 FSM</strong>：它利用 <code>interegular</code> 等库，将复杂的规则（如 JSON 语法或正则）转换成一个高度优化的<strong>有限状态机</strong>。</li><li><strong>状态映射</strong>：在这个状态机中，每个状态都代表了当前已经生成的字符串序列。状态机明确知道：在当前状态下，下一个字符可以是哪些，不可以是哪些。</li><li>FSM的缺点是无法准确表示复杂的schema。</li></ol><p><strong>guidance</strong></p><p>Guidance 的本质是将 <strong>确定性的程序逻辑</strong> 与 <strong>不确定性的 AI 生成</strong> 缝合在一起。</p><p>在生成过程中，Guidance 采取“接力赛”模式：</p><ul><li><strong>程序控制阶段</strong>：框架直接向模型注入确定的字符串（如 JSON 的键名 <code>{&quot;name&quot;:</code>）。这部分不消耗模型的推理计算，是直接“贴”上去的。</li><li><strong>模型生成阶段</strong>：框架将控制权交给 AI，但限制 AI 只能在指定的“坑”里填空。填完后，框架立刻收回控制权，跳过冗余字符，直接进入下一个字段。</li></ul><p><strong>Instructor</strong></p><p>主要依赖于 <strong>Pydantic</strong>、<strong>Python 动态类型</strong>以及底层模型的 <strong>Function Calling</strong> 协议</p><p><strong>① 模式转换 (Model to Schema)</strong></p><p>当你定义一个 <code>Pydantic</code> 模型并传给 Instructor 时，它会利用 Pydantic 内置的 <code>model_json_schema()</code> 方法，自动将这个 Python 类转换成标准的 <strong>JSON Schema</strong>。</p><p><strong>② 协议适配 (Protocol Adaptation)</strong></p><p>Instructor 会根据你使用的模型，将 Schema 包装进正确的 API 参数中：</p><ul><li><strong>对于 OpenAI</strong>：它会将 Schema 放入 <code>tools</code> 或新的 <code>response_format</code>（Structured Outputs）中。</li><li><strong>对于 Anthropic/Gemini</strong>：它会调整为对应的 <code>Tool Use</code> 或 <code>JSON Mode</code> 格式。</li></ul><p><strong>③ 采样约束与解析 (Sampling &amp; Parsing)</strong></p><p>模型返回 JSON 字符串后，Instructor 会立即进行 <strong>反序列化</strong>。</p><ul><li>它尝试用你定义的 Pydantic 模型去实例化这个 JSON。</li><li><strong>关键点</strong>：如果 JSON 缺少字段或类型不对，Pydantic 会抛出验证错误（ValidationError）。</li></ul><p><strong>④ 自动重试机制 (Self-Correction/Retries)</strong></p><p>这是 Instructor 最强大的功能。如果解析失败，它不会直接报错，而是：</p><ol><li>把 Pydantic 抛出的<strong>具体错误信息</strong>（例如：<code>age 字段缺失</code>）作为新的 User Message 发回给 AI。</li><li>告诉 AI：“你刚才的输出不对，报错如下，请修正后重新生成。”</li><li>这个过程会重复 N 次，直到获取合规数据。</li></ol><h3 id="生产实现"><a href="#生产实现" class="headerlink" title="生产实现"></a>生产实现</h3><p>在 Java 生态中，尤其是 <strong>LangChain4j</strong> 和 <strong>Spring AI</strong> 实现结构化输出的底层逻辑，其实是把“复杂的协议通信”封装成了开发者熟悉的“对象映射”。</p><p>实现方式主要分为两类：<strong>基于 API 协议的强约束</strong>（类似 OpenAI 官方方案）和 <strong>基于 Prompt 的后置解析</strong>。</p><ol><li>强约束方案：<strong>基于 Function Calling</strong> (推荐)</li></ol><p>这是 LangChain4j 等库默认的首选方式。它<strong>利用了模型厂商提供的专用接口</strong>。</p><ul><li><strong>实现步骤</strong>：<ol><li><strong>内省 (Introspection)</strong>：当你定义一个 Java <code>Record</code> 或 <code>POJO</code> 时，框架会利用 Java 的<strong>反射机制</strong>读取字段名、类型和 <code>@Description</code> 注解。</li><li><strong>Schema 转换</strong>：框架将这些 Java 信息翻译成 <strong>JSON Schema</strong>。</li><li><strong>协议请求</strong>：在调用模型 API 时，框架不会把 Schema 塞进 Prompt，而是塞进参数里的 <code>tools</code> 或 <code>response_format</code> 字段。</li><li><strong>模型输出</strong>：由于模型底层开启了“受限采样”，它会直接吐出一个干净的 JSON 字符串。</li><li><strong>反序列化</strong>：框架内部使用 <strong>Jackson</strong> 或 <strong>Gson</strong> 将 JSON 字符串瞬间转回 Java 对象。</li></ol></li></ul><ol><li>弱约束方案：基于指令与 Parser (Spring AI 常用)</li></ol><p>如果模型<strong>不支持强约束接口（如某些早期的开源模型），Java 框架会回退到这种模式</strong>。</p><ul><li><strong>实现步骤</strong>：<ol><li><strong>注入指令</strong>：<code>BeanOutputParser</code> 会生成一段非常长且严厉的 Prompt 片段，告诉 AI：“你必须只返回 JSON，字段必须叫 XXX，类型必须是 int…”。</li><li><strong>后置处理</strong>：AI 返回一段包含 JSON 的文本。</li><li><strong>正则提取</strong>：Java 框架会用正则表达式从一堆废话中捞出 <code>{ ... }</code> 这一部分。</li><li><strong>验证与纠错</strong>：如果 Jackson 转换失败，部分框架（如 LangChain4j）会把错误日志发回给 AI，说“你刚才生成的 JSON 少了个逗号，请重写”，这就是自动修复机制。</li></ol></li></ul><h2 id="有效评估与改进RAG应用"><a href="#有效评估与改进RAG应用" class="headerlink" title="有效评估与改进RAG应用"></a>有效评估与改进RAG应用</h2><p>首先我们要明确业务目标，然后根据业务目标制定指标，再根据实际的指标值改进检索技术。这种方法一步到位，</p><p><strong>RAGAS</strong></p><ol><li>与竞品相比，文档相对较完备。</li><li>专业度比较高，专注于做RAG评测。</li><li>支持与LLamaIndex、LangChain等11种RAG框架集成。</li></ol><p>Ragas目前实现了十项评估指标，我们挨个来看看。</p><p><strong>忠实度（Faithfulness）</strong>指标用于衡量生成答案与给定上下文的事实一致性。如果生成答案中的所有声明都可以从给定的上下文中推断出来，则认为该答案是忠实的。</p><p><strong>答案相关性（Answer Relevance）</strong>侧重于评估生成答案与给定提示的相关性。对于不完整或包含冗余信息的答案，会给出较低的分数，而较高的分数表示更好的相关性。这个指标是通过问题、上下文和答案来计算的。答案相关性定义为原始问题与基于答案生成（逆向工程）的若干人工问题之间的平均余弦相似度。</p><p><strong>上下文查准率（Context Precision）</strong>用于判断上下文中存在的所有真实相关项，是否都排在了较高的位置。理想情况下，所有相关的信息块都应该出现在顶部排名。这个指标是通过问题、真实答案和上下文来计算的，其值范围在0到1之间，分数越高表示查准率越高。</p><p><strong>上下文利用率</strong>（Context utilization）：上下文利用就像是上下文查准率指标的无参考版本。也就是关注是否利用了所有可用的信息块，而忽略掉它们的顺序如何。</p><p><strong>上下文查全率（Context Recall）</strong>，它衡量的是检索到的上下文与作为真实答案的标注答案的一致程度。该指标是通过问题、真实答案和检索到的上下文来计算的，其值范围在0到1之间，数值越高表示性能越好。为了从真实答案中估计上下文召回率，会分析真实答案中的每个声明，以确定它是否可以归因于检索到的上下文。在理想情况下，真实答案中的所有声明都应该可以归因于检索到的上下文。</p><p><strong>上下文实体查全率（Context entities Recall）</strong>，衡量从真实答案中召回的实体比例的一个指标。具体就是根据真实答案和上下文中存在的实体数量相对于单独在真实答案中存在的实体数量，来衡量检索到的上下文的召回率。</p><p>这个指标在有事实依据的用例中很有用，比如旅游咨询台、历史问答等。这个指标可以帮助评估基于与真实答案中实体的比较的实体检索机制，因为在实体重要的情况下，我们需要覆盖这些实体的上下文。</p><p><strong>答案语义相似度（Answer semantic similarity）</strong>，用于评估生成答案与真实答案之间的语义相似度。这个评估是基于真实答案和答案进行的，其值在0到1的范围内。分数越高，表示生成的答案与真实答案间的一致性越好。</p><p><strong>答案正确度（Answer Correctness）</strong>，用衡量生成的答案与真实答案相比的准确性。这个评估依赖于真实答案和答案，分数范围从0到1。分数越高表示生成的答案与真实答案之间的一致性越好，意味着正确度更高。</p><p>答案正确度包括两个关键方面：生成答案与真实答案之间的语义相似度以及事实相似度。这些方面通过加权方案结合起来，形成答案正确性分数。用户还可以选择使用一个“阈值”值将结果分数四舍五入为二进制（如果需要）。</p><p><strong>特定领域评估（Domain Specific Evaluation）</strong>，特定领域评估指标用于评估模型在特定领域的性能。评分标准包含了每个分数的描述，通常范围在从1到5分。</p><p><strong>摘要分数（Summarization Score）</strong>，这个指标衡量摘要在捕捉上下文重要信息方面的表现如何。这个指标背后的直觉是，一个好的摘要应该包含上下文所有重要信息。我们首先从上下文中提取一组重要的关键词。然后使用这些关键词生成一组问题。接着我们向摘要提出这些问题，并计算摘要得分为正确回答的问题数与问题总数的比率。</p><p>现如果你想直接把刚刚这些Ragas指标用到我们前面实战案例的评估，就会发现门槛还是有点高，难度比较大。为什么呢？因为我们没有评估所需要的基础数据，所以我们需要先采集相关数据。</p><p>那如何采集评估所需要的基础数据呢？一种方法是让数据标注员人工提问和标注，这种方法成本很高。目前最常见，也是最容易的方法是让用户直接提供反馈，然后收集这些结果，再让数据标注员分析和标注。</p><p> Precision、Recall、F1、MRR</p><p>这三个指标通常用于评估二分类模型或非排序的检索结果。</p><ul><li><p><strong>Precision (精确率/查准率)</strong>：</p><p>衡量“检索出的结果中，有多少是真正相关的”。</p><script type="math/tex; mode=display">Precision = \frac{TP}{TP + FP}</script></li><li><p><strong>Recall (召回率/查全率)</strong>：</p><p>衡量“所有真正相关的结果中，有多少被成功找回了”。</p><script type="math/tex; mode=display">Recall = \frac{TP}{TP + FN}</script></li><li><p><strong>F1-Score</strong>：</p><p>精确率和召回率的调和平均数。当两者出现冲突时（例如一个高一个低），$F1$ 提供了一个平衡的评估标准。</p><script type="math/tex; mode=display">F1 = 2 \cdot \frac{Precision \cdot Recall}{Precision + Recall}</script></li></ul><p>在搜索和推荐场景中，相关条目的<strong>排名越靠前</strong>，用户体验越好。</p><ul><li><p><strong>MRR (Mean Reciprocal Rank, 平均倒数排名)</strong>：</p><p>它只关注<strong>第一个</strong>相关结果出现的位置。</p><ul><li><p><strong>计算逻辑</strong>：对于一个查询，如果第一个相关结果排在第 $n$ 位，其倒数排名就是 $1/n$。将所有查询的倒数排名取平均值即为 MRR。</p></li><li><p><strong>公式</strong>：</p><script type="math/tex; mode=display">MRR = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{rank_i}</script></li><li><p><strong>特点</strong>：MRR 越高，说明系统能越快地让用户找到想要的第一个信息。</p></li></ul></li></ul><p> RRF 倒数排名</p><p><strong>RRF (Reciprocal Rank Fusion, 倒数排名融合)</strong> 严格来说不是一种单纯的“评估指标”，而是一种将<strong>多个排序列表融合为一个</strong>的算法。它在混合搜索（Hybrid Search，如：关键词检索 + 向量检索）中非常流行。</p><ul><li><p><strong>原理</strong>：它不依赖各个检索系统给出的具体分数（因为不同系统的分数尺度不同，无法直接相加），而是只依赖它们的<strong>排名</strong>。</p></li><li><p><strong>核心公式</strong>：</p><p>对于文档 $d$，其在所有排名列表 $R$ 中的总分为：</p><script type="math/tex; mode=display">RRFscore(d) = \sum_{r \in R} \frac{1}{k + r(d)}</script><ul><li>$r(d)$：文档 $d$ 在列表 $r$ 中的排名位置。</li><li>$k$：一个常数（平滑因子），通常取 <strong>60</strong>。</li></ul></li></ul><blockquote><p><strong>为什么有用？</strong></p><p>RRF 具有“二八定律”的特性：排在前面的文档权重会迅速衰减，而多个列表共同排在前面的文档会获得极高的总分。它能非常简单且有效地结合语义搜索和传统搜索的优点。</p></blockquote><h2 id="其他RAG技术"><a href="#其他RAG技术" class="headerlink" title="其他RAG技术"></a>其他RAG技术</h2><p><strong>GraphRAG</strong></p><p>GraphRAG是一种结合了知识图谱的检索增强生成技术，旨在通过构建知识图谱和社区检测算法，提升大模型在理解和生成复杂信息方面的能力。它通过<strong>图结构信息</strong>，能够更精确地检索和生成与上下文相关的回答，从而在处理大规模数据集时展现出显著的性能提升</p><h2 id="规范化RAG应用"><a href="#规范化RAG应用" class="headerlink" title="规范化RAG应用"></a>规范化RAG应用</h2><h3 id="RAG链路级优化"><a href="#RAG链路级优化" class="headerlink" title="RAG链路级优化"></a>RAG链路级优化</h3><p><img data-src="https://s2.loli.net/2026/01/13/BmrKg4o7uCNx3fZ.png" alt="image-20260113192018389"></p><p>从知识入库处理到最终生成评估.</p><h1 id="SpringAI智能面试平台-RAG"><a href="#SpringAI智能面试平台-RAG" class="headerlink" title="SpringAI智能面试平台+RAG"></a>SpringAI智能面试平台+RAG</h1><h2 id="基于Tika实现多格式内容提取与解析"><a href="#基于Tika实现多格式内容提取与解析" class="headerlink" title="基于Tika实现多格式内容提取与解析"></a>基于Tika实现多格式内容提取与解析</h2><h2 id="工具调用"><a href="#工具调用" class="headerlink" title="工具调用"></a>工具调用</h2><p>Function Calling（函数调用）是让 LLM 能够使用外部工具的核心机制。Function Calling允许模型决定<strong>何时调用工具</strong>、<strong>调用哪个工具</strong>，以及<strong>传递什么参数</strong>。</p><p><img data-src="https://www.runoob.com/wp-content/uploads/2026/02/runoob-funcall-runoob-scaled.png" alt="img" style="zoom:50%;" />核心概念</p><ul><li><strong>工具定义</strong>：描述一个工具的功能、参数和返回值</li><li><strong>工具选择</strong>：LLM 根据用户问题选择合适的工具</li><li><strong>参数提取</strong>：LLM 从问题中提取工具所需的参数</li><li><strong>结果处理</strong>：将工具执行结果整合到最终回答中</li></ul><p>假设我们有一个查询天气的工具，当用户问<strong>北京今天天气如何？</strong>时：</p><ol><li>LLM 识别出需要调用天气查询工具</li><li>从问题中提取参数：<code>city=&quot;北京&quot;</code>, <code>date=&quot;今天&quot;</code></li><li>调用天气 API 获取数据</li><li>将天气数据整合成友好的回答返回给用户</li></ol><p>要让 LLM 正确使用工具，首先需要清晰地定义工具，好的工具定义应该像一份清晰的说明书，让 LLM 明白：</p><ul><li>这个工具是做什么的？</li><li>什么时候使用它？</li><li>需要什么参数？</li><li>参数是什么格式？</li></ul><p>工具定义的结构</p><p>一个完整的工具定义通常包含以下部分：</p><figure class="highlight makefile"><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 class="comment"># 工具定义示例结构</span></span><br><span class="line">weather_tool = &#123;</span><br><span class="line">  <span class="string">&quot;name&quot;</span>: <span class="string">&quot;get_weather&quot;</span>, <span class="comment"># 工具名称</span></span><br><span class="line">  <span class="string">&quot;description&quot;</span>: <span class="string">&quot;获取指定城市的天气信息&quot;</span>, <span class="comment"># 工具描述</span></span><br><span class="line">  <span class="string">&quot;parameters&quot;</span>: &#123; <span class="comment"># 参数定义</span></span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;object&quot;</span>,</span><br><span class="line">    <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;city&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">        <span class="string">&quot;description&quot;</span>: <span class="string">&quot;城市名称，如&#x27;北京&#x27;、&#x27;上海&#x27;&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&quot;date&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">        <span class="string">&quot;description&quot;</span>: <span class="string">&quot;日期，格式&#x27;YYYY-MM-DD&#x27;，或&#x27;今天&#x27;、&#x27;明天&#x27;&quot;</span>,</span><br><span class="line">        <span class="string">&quot;enum&quot;</span>: [<span class="string">&quot;今天&quot;</span>, <span class="string">&quot;明天&quot;</span>, <span class="string">&quot;后天&quot;</span>]</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;required&quot;</span>: [<span class="string">&quot;city&quot;</span>] <span class="comment"># 必填参数</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="难点-1：参数提取不准确（最常见）"><a href="#难点-1：参数提取不准确（最常见）" class="headerlink" title="难点 1：参数提取不准确（最常见）"></a>难点 1：参数提取不准确（最常见）</h4><ul><li>问题表现：模型漏提参数、提错参数类型（比如把 “北京” 识别成数字）、参数值不符合规则（比如城市名写成 “北京市市”）。</li><li><p>典型场景：用户说 “查下上海明天的空气质量”，模型可能漏提 “明天” 这个时间参数，只调用 <code>get_air_quality(city=&quot;上海&quot;)</code>，导致返回今天的数据。</p><p>解决 “参数提取不准确”：结构化提示 + 参数校验</p></li><li><p>核心思路：</p><ol><li>在提示词中明确参数的<strong>名称、类型、约束条件</strong>（比如城市名只能是中文，时间格式为 YYYY-MM-DD）；</li><li>调用工具前加一层<strong>参数校验逻辑</strong>，不符合规则就让模型重新提取。</li></ol></li><li><p>示例提示词：</p><figure class="highlight axapta"><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></pre></td><td class="code"><pre><span class="line">你需要调用工具解决用户问题，规则如下：</span><br><span class="line"><span class="number">1.</span> 工具：get_weather(city: <span class="built_in">str</span>, <span class="built_in">date</span>: <span class="built_in">str</span>)</span><br><span class="line"><span class="number">2.</span> 参数约束：</span><br><span class="line">   - city：必须是中国城市的中文全称，如“北京”“上海”，不能加“市”；</span><br><span class="line">   - <span class="built_in">date</span>：可选，格式为YYYY-MM-DD，默认是当天。</span><br><span class="line"><span class="number">3.</span> 若参数缺失或不符合规则，必须重新追问用户或修正。</span><br></pre></td></tr></table></figure></li></ul><p><strong>参数校验逻辑</strong></p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">validate_parameters</span>(<span class="params">func_name, params</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;校验函数参数&quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># 定义各函数的参数规则</span></span><br><span class="line">    func_rules = &#123;</span><br><span class="line">        <span class="string">&quot;get_weather&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;city&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="built_in">str</span>, <span class="string">&quot;required&quot;</span>: <span class="literal">True</span>, <span class="string">&quot;pattern&quot;</span>: <span class="string">r&quot;^[\u4e00-\u9fa5]&#123;2,4&#125;$&quot;</span>&#125;,</span><br><span class="line">            <span class="string">&quot;date&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="built_in">str</span>, <span class="string">&quot;required&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;pattern&quot;</span>: <span class="string">r&quot;^\d&#123;4&#125;-\d&#123;2&#125;-\d&#123;2&#125;$&quot;</span>&#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> func_name <span class="keyword">not</span> <span class="keyword">in</span> func_rules:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span>, <span class="string">&quot;工具不存在&quot;</span></span><br><span class="line">    </span><br><span class="line">    rules = func_rules[func_name]</span><br><span class="line">    <span class="keyword">for</span> param, rule <span class="keyword">in</span> rules.items():</span><br><span class="line">        <span class="comment"># 检查必填参数</span></span><br><span class="line">        <span class="keyword">if</span> rule[<span class="string">&quot;required&quot;</span>] <span class="keyword">and</span> param <span class="keyword">not</span> <span class="keyword">in</span> params:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span>, <span class="string">f&quot;缺失必填参数：<span class="subst">&#123;param&#125;</span>&quot;</span></span><br><span class="line">        <span class="comment"># 检查参数类型</span></span><br><span class="line">        <span class="keyword">if</span> param <span class="keyword">in</span> params <span class="keyword">and</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(params[param], rule[<span class="string">&quot;type&quot;</span>]):</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span>, <span class="string">f&quot;<span class="subst">&#123;param&#125;</span>类型错误，需为<span class="subst">&#123;rule[<span class="string">&#x27;type&#x27;</span>].__name__&#125;</span>&quot;</span></span><br><span class="line">        <span class="comment"># 检查参数格式（如正则）</span></span><br><span class="line">        <span class="keyword">if</span> param <span class="keyword">in</span> params <span class="keyword">and</span> rule.get(<span class="string">&quot;pattern&quot;</span>) <span class="keyword">and</span> <span class="keyword">not</span> re.<span class="keyword">match</span>(rule[<span class="string">&quot;pattern&quot;</span>], params[param]):</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span>, <span class="string">f&quot;<span class="subst">&#123;param&#125;</span>格式错误，需符合规则：<span class="subst">&#123;rule[<span class="string">&#x27;pattern&#x27;</span>]&#125;</span>&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span>, <span class="string">&quot;参数校验通过&quot;</span></span><br></pre></td></tr></table></figure><h4 id="2-难点-2：输出格式不规范"><a href="#2-难点-2：输出格式不规范" class="headerlink" title="2. 难点 2：输出格式不规范"></a>2. 难点 2：输出格式不规范</h4><ul><li>问题表现：模型输出的调用指令不符合程序解析要求（比如 JSON 格式缺引号、多逗号、字段名写错），导致代码解析失败。</li><li>典型场景：要求输出 <code>{&quot;name&quot;:&quot;get_weather&quot;,&quot;parameters&quot;:{&quot;city&quot;:&quot;北京&quot;}}</code>，模型却输出 <code>{name:get_weather, parameters:{city:北京}}</code>（缺引号），程序直接报错。</li></ul><p>解决 “输出格式不规范”：强制格式约束 + JSON 模式</p><p>核心思路：</p><ol><li>用<strong>JSON Schema</strong> 定义输出格式（明确字段名、类型），让模型严格遵循；</li><li>主流大模型（GPT-3.5/4、Claude、文心一言）都支持 “强制 JSON 输出”，直接指定格式即可。</li></ol><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line">client = OpenAI()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义工具描述（含JSON Schema）</span></span><br><span class="line">tools = [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;function&quot;</span>,</span><br><span class="line">        <span class="string">&quot;function&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;name&quot;</span>: <span class="string">&quot;get_weather&quot;</span>,</span><br><span class="line">            <span class="string">&quot;description&quot;</span>: <span class="string">&quot;获取指定城市指定日期的气温&quot;</span>,</span><br><span class="line">            <span class="string">&quot;parameters&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;type&quot;</span>: <span class="string">&quot;object&quot;</span>,</span><br><span class="line">                <span class="string">&quot;properties&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;city&quot;</span>: &#123;</span><br><span class="line">                        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;description&quot;</span>: <span class="string">&quot;中国城市中文全称，如北京、上海&quot;</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    <span class="string">&quot;date&quot;</span>: &#123;</span><br><span class="line">                        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;description&quot;</span>: <span class="string">&quot;日期，格式为YYYY-MM-DD，可选，默认当天&quot;</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="string">&quot;required&quot;</span>: [<span class="string">&quot;city&quot;</span>]  <span class="comment"># 明确必填参数</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用API，强制JSON输出</span></span><br><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">&quot;gpt-3.5-turbo&quot;</span>,</span><br><span class="line">    messages=[&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: <span class="string">&quot;查下北京明天的气温&quot;</span>&#125;],</span><br><span class="line">    tools=tools,</span><br><span class="line">    tool_choice=<span class="string">&quot;auto&quot;</span>,  <span class="comment"># 让模型自动选择是否调用工具</span></span><br><span class="line">    response_format=&#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;json_object&quot;</span>&#125;  <span class="comment"># 强制JSON格式</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解析调用结果</span></span><br><span class="line">tool_call = response.choices[<span class="number">0</span>].message.tool_calls[<span class="number">0</span>]</span><br><span class="line">func_name = tool_call.function.name</span><br><span class="line">func_params = json.loads(tool_call.function.arguments)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;调用工具：<span class="subst">&#123;func_name&#125;</span>，参数：<span class="subst">&#123;func_params&#125;</span>&quot;</span>)</span><br><span class="line"><span class="comment"># 输出：调用工具：get_weather，参数：&#123;&quot;city&quot;:&quot;北京&quot;,&quot;date&quot;:&quot;2026-02-23&quot;&#125;</span></span><br></pre></td></tr></table></figure><p>大模型的 “强制 JSON 输出” 不是单一技术，而是<strong>训练阶段的格式对齐 + 推理阶段的约束机制</strong> 共同作用的结果，</p><h2 id="训练阶段：给模型-“植入”-JSON-语法认知"><a href="#训练阶段：给模型-“植入”-JSON-语法认知" class="headerlink" title="训练阶段：给模型 “植入” JSON 语法认知"></a>训练阶段：给模型 “植入” JSON 语法认知</h2><p>这是所有模型能输出合法 JSON 的基础，也是 “强制输出” 的底层前提。</p><p> 数据层面：大规模 JSON 格式数据注入</p><ul><li><p>做法</p><p>：在模型预训练 / 微调阶段，加入海量的 “自然语言 ↔ JSON” 配对数据，比如：</p><ul><li>输入：“提取北京明天的气温” → 输出：<code>{&quot;city&quot;:&quot;北京&quot;,&quot;date&quot;:&quot;明天&quot;,&quot;action&quot;:&quot;查气温&quot;}</code>；</li><li>输入：“计算 1+2” → 输出：<code>{&quot;operation&quot;:&quot;add&quot;,&quot;num1&quot;:1,&quot;num2&quot;:2,&quot;result&quot;:3}</code>；</li></ul></li><li><p><strong>目的</strong>：让模型学习 “自然语言需求” 和 “合法 JSON 结构” 的强映射关系，理解 JSON 的语法规则（双引号、键值对、无多余逗号等）。</p></li><li><p>关键细节：</p><ul><li>数据中会包含大量 “错误 JSON 案例 + 修正结果”（比如缺少双引号的 JSON → 模型输出修正后的合法 JSON）；</li><li>对 GPT-4 这类原生支持 JSON 的模型，还会单独构建 “JSON 语法专项数据集”（包含 JSON Schema 约束、嵌套 JSON、数组等复杂场景）。</li></ul></li></ul><p>模型架构层面：针对性的格式约束模块（GPT 专属）</p><p>OpenAI 为 GPT-3.5-turbo-1106/4 新增了<strong>格式约束头（Format Constraint Head）</strong>，这是其 “原生强制 JSON” 的核心：</p><ul><li><strong>位置</strong>：在模型的 decoder 层最后，加一个独立的 “语法校验模块”；</li><li>作用：<ol><li>接收上层输出的 token 序列（比如 <code>{&quot;city&quot;:&quot;北京&quot;</code>）；</li><li>实时校验是否符合 JSON 语法规则（比如是否漏双引号、是否有多余逗号）；</li><li>对违规的 token 进行 “抑制”（比如模型想输出单引号 <code>&#39;北京&#39;</code>，该模块会降低单引号 token 的概率，提升双引号 <code>&quot;</code> 的概率）；</li></ol></li><li><strong>对比</strong>：Claude / 文心一言暂未在模型架构层加这类硬件模块，而是靠后续推理阶段的规则弥补。</li></ul><p>微调阶段：指令调优（RLHF）强化 JSON 格式</p><ul><li><p>做法</p><p>：在人类反馈强化学习（RLHF）阶段，加入 “JSON 格式正确性” 的奖励机制：</p><ul><li>如果模型输出合法 JSON → 给予高奖励（提升该输出路径的概率）；</li><li>如果输出非法 JSON（比如缺引号、多逗号）→ 给予惩罚（降低该路径概率）；</li></ul></li><li><p><strong>GPT 额外操作</strong>：对 “response_format=json_object” 这类参数，单独做了 “指令调优”，让模型识别到该参数后，直接激活 “JSON 约束模式”。</p></li></ul><p>推理阶段：实时约束输出，确保 JSON 合法</p><p>训练阶段是 “让模型会写 JSON”，推理阶段是 “强制模型必须写合法 JSON”，这是 “强制输出” 的核心执行层。</p><ol><li>GPT 系列：原生语法锁（硬限制）</li></ol><p>当调用 <code>response_format={&quot;type&quot;:&quot;json_object&quot;}</code> 时，模型推理过程会触发以下底层逻辑：模型无法输出任何非 JSON 内容（比如 “以下是结果：” 这类文字），因为格式约束头会直接过滤掉这类 token；</p><p>如果模型无法生成合法 JSON（比如需求矛盾），会直接终止推理并返回 API 错误，而非输出错误 JSON；</p><p>生成过程中会自动补全 JSON 缺失部分（比如模型生成 <code>{&quot;city&quot;:&quot;北京&quot;</code>，会自动补全 <code>}</code></p><p>2.Claude 系列：提示词引导 + 生成后校验（软限制）</p><p>Claude 没有模型层的 “JSON 语法锁”，其 “强制输出” 靠两步实现：</p><ol><li>推理初期：提示词解析为格式规则<ul><li>模型先解析用户提示词中的 JSON 要求（比如 “仅输出 JSON”“字段名双引号”），将其转化为内部的 “生成规则”；</li><li>生成时，模型会优先选择符合 JSON 语法的 token（基于训练阶段的认知）。</li></ul></li><li>推理后期：生成后语法校验<ul><li>Claude 生成完文本后，会有一个轻量级的 “JSON 校验模块”（属于推理框架，非模型层）；</li><li>如果检测到 JSON 非法（比如缺逗号），会自动修正后输出；如果无法修正，会重新生成。</li></ul></li></ol><ul><li><strong>特点</strong>：依赖提示词的清晰度，没有 GPT 那样的 “硬锁”，但通过训练数据的强引导，成功率接近 100%。</li></ul><p>不管是哪种模型，“强制 JSON 输出” 最终都会落到 “token 生成的概率控制” 上（这是模型推理的最底层）：</p><ol><li>Token 概率重加权</li></ol><ul><li><p>做法</p><p>：在生成每个 token 时，调整不同 token 的概率：</p><ul><li>提升 JSON 合规 token 的概率（比如 <code>&quot;</code>、<code>{</code>、<code>}</code>、<code>,</code>）；</li><li>降低违规 token 的概率（比如 <code>&#39;</code>、<code>：</code>、中文逗号 <code>，</code>、多余的空格）；</li></ul></li></ul><ol><li>上下文窗口的格式记忆</li></ol><ul><li>模型会在推理的上下文窗口中，“记住” 当前的 JSON 结构状态：<ul><li>比如已经生成了 <code>{</code>，模型会记住 “需要闭合 <code>}</code>”；</li><li>生成了 <code>&quot;city&quot;:&quot;北京&quot;</code>，会记住 “下一个可能是 <code>,</code> 或 <code>}</code>”；</li></ul></li><li>GPT 的格式约束头会专门维护这个 “JSON 状态机”，而 Claude 靠训练数据的记忆实现。</li></ul><ol><li>难点 3：工具匹配错误</li></ol><ul><li>问题表现：模型选错工具（比如用户要算加法，却调用了乘法工具），或没必要调用工具时强行调用（比如用户问 “1+1 等于几”，本可直接回答，却调用计算器工具）。</li></ul><p>核心思路：</p><ol><li>给每个工具写<strong>精准、唯一的描述</strong>（避免模糊，比如 “get_weather” 描述为 “仅用于获取城市气温，不包含空气质量、风力”）；</li><li><p>对高风险调用（比如涉及付费 API），增加 “模型自我确认” 步骤，让模型先判断 “是否需要调用工具”“是否选对工具”。</p></li><li><p>难点 4：上下文丢失</p></li></ol><ul><li>问题表现：多轮对话中，模型忘记上一轮的参数（比如用户先问 “北京气温”，接着说 “空气质量呢”，模型没识别出 “北京” 这个上下文参数）。</li></ul><p>核心思路：</p><ol><li>把多轮对话中的参数缓存到上下文（比如用户先提 “北京”，后续对话自动带上<code>city=&quot;北京&quot;</code>）；</li><li>若参数缺失，让模型主动追问用户（比如用户说 “查气温”，模型回复 “请问你想查哪个城市的气温？”）</li></ol><h2 id="记忆系统"><a href="#记忆系统" class="headerlink" title="记忆系统"></a>记忆系统</h2><p>人类的记忆分为短期记忆和长期记忆，AI Agent 的记忆系统也采用了类似的设计。</p><p>理解这两种记忆的区别和用途，是构建智能 Agent 的基础。</p><p>短期记忆：Agent 的工作记忆</p><p>短期记忆就像是 Agent 的工作台，存储当前对话的上下文和临时信息。</p><p><strong>特点</strong>：</p><ul><li><strong>容量有限</strong>：通常只能保存最近的几次对话（如最近的 10-20 轮对话）</li><li><strong>快速访问</strong>：读取和写入速度很快</li><li><strong>临时性</strong>：对话结束后通常会被清除或压缩</li><li><strong>上下文相关</strong>：直接影响当前的响应生成</li></ul><p>长期记忆：Agent 的知识库</p><p>长期记忆就像是 Agent 的档案室，存储重要的、需要长期保留的信息。</p><p><strong>特点</strong>：</p><ul><li><strong>容量大</strong>：可以存储大量信息</li><li><strong>持久化</strong>：信息会长期保存，不会自动清除</li><li><strong>检索式访问</strong>：通过查询检索相关信息，而非顺序读取</li><li><strong>结构化存储</strong>：信息通常以结构化方式存储，便于检索</li></ul><p><strong>主要用途</strong>：</p><ul><li>存储用户的个人信息和偏好</li><li>积累知识和经验</li><li>记住重要的对话内容</li><li>保存任务执行结果</li></ul><h3 id="对话历史管理"><a href="#对话历史管理" class="headerlink" title="对话历史管理"></a>对话历史管理</h3><h3 id="基本的对话历史管理"><a href="#基本的对话历史管理" class="headerlink" title="基本的对话历史管理"></a>基本的对话历史管理</h3><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ConversationManager</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;对话管理器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, max_context_length=<span class="number">4000</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.max_context_length = max_context_length  <span class="comment"># 最大token数</span></span><br><span class="line">        <span class="variable language_">self</span>.history = []  <span class="comment"># 完整的对话历史</span></span><br><span class="line">        <span class="variable language_">self</span>.active_context = []  <span class="comment"># 当前活跃的上下文</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_exchange</span>(<span class="params">self, user_input, assistant_response</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;添加一轮对话&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.history.append(&#123;</span><br><span class="line">            <span class="string">&quot;user&quot;</span>: user_input,</span><br><span class="line">            <span class="string">&quot;assistant&quot;</span>: assistant_response,</span><br><span class="line">            <span class="string">&quot;timestamp&quot;</span>: time.time()</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">build_context</span>(<span class="params">self, current_query, include_history=<span class="literal">True</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;构建当前查询的上下文&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> include_history <span class="keyword">or</span> <span class="keyword">not</span> <span class="variable language_">self</span>.history:</span><br><span class="line">            <span class="comment"># 没有历史或不需要历史，只返回当前查询</span></span><br><span class="line">            <span class="keyword">return</span> [&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: current_query&#125;]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 从最近的对话开始，逐步添加历史，直到达到长度限制</span></span><br><span class="line">        context = []</span><br><span class="line">        context_length = <span class="variable language_">self</span>.estimate_tokens(current_query)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 添加当前查询</span></span><br><span class="line">        context.insert(<span class="number">0</span>, &#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: current_query&#125;)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 从最近到最远添加历史</span></span><br><span class="line">        <span class="keyword">for</span> exchange <span class="keyword">in</span> <span class="built_in">reversed</span>(<span class="variable language_">self</span>.history):</span><br><span class="line">            user_tokens = <span class="variable language_">self</span>.estimate_tokens(exchange[<span class="string">&quot;user&quot;</span>])</span><br><span class="line">            assistant_tokens = <span class="variable language_">self</span>.estimate_tokens(exchange[<span class="string">&quot;assistant&quot;</span>])</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 检查是否会超出限制</span></span><br><span class="line">            <span class="keyword">if</span> context_length + user_tokens + assistant_tokens &gt; <span class="variable language_">self</span>.max_context_length:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 添加助理回复（在用户输入之前）</span></span><br><span class="line">            context.insert(<span class="number">0</span>, &#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;assistant&quot;</span>, <span class="string">&quot;content&quot;</span>: exchange[<span class="string">&quot;assistant&quot;</span>]&#125;)</span><br><span class="line">            context.insert(<span class="number">0</span>, &#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: exchange[<span class="string">&quot;user&quot;</span>]&#125;)</span><br><span class="line"></span><br><span class="line">            context_length += user_tokens + assistant_tokens</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> context</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">estimate_tokens</span>(<span class="params">self, text</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;粗略估计文本的token数量（实际应用中应使用准确的tokenizer）&quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># 简单估算：英文约0.75单词/token，中文约1-2字符/token</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.is_chinese(text):</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">len</span>(text) // <span class="number">2</span>  <span class="comment"># 中文每2字符约1个token</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            words = <span class="built_in">len</span>(text.split())</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">int</span>(words * <span class="number">1.3</span>)  <span class="comment"># 英文每单词约1.3个token</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">is_chinese</span>(<span class="params">self, text</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;判断文本是否主要为中文&quot;&quot;&quot;</span></span><br><span class="line">        chinese_chars = <span class="built_in">sum</span>(<span class="number">1</span> <span class="keyword">for</span> c <span class="keyword">in</span> text <span class="keyword">if</span> <span class="string">&#x27;\u4e00&#x27;</span> &lt;= c &lt;= <span class="string">&#x27;\u9fff&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> chinese_chars / <span class="built_in">max</span>(<span class="built_in">len</span>(text), <span class="number">1</span>) &gt; <span class="number">0.3</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">clear_history</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;清空对话历史&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.history.clear()</span><br><span class="line">        <span class="variable language_">self</span>.active_context.clear()</span><br></pre></td></tr></table></figure><h3 id="智能历史选择策略"><a href="#智能历史选择策略" class="headerlink" title="智能历史选择策略"></a>智能历史选择策略</h3><p>不是所有历史对话都同样重要。智能的历史选择可以提高上下文的使用效率：</p><h4 id="1-基于时间的衰减"><a href="#1-基于时间的衰减" class="headerlink" title="1. 基于时间的衰减"></a>1. 基于时间的衰减</h4><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">time_based_selection</span>(<span class="params">history, current_time, max_items=<span class="number">10</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;基于时间的选择：越近的对话权重越高&quot;&quot;&quot;</span></span><br><span class="line">    scored_items = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> item <span class="keyword">in</span> history:</span><br><span class="line">        <span class="comment"># 计算时间衰减分数（越近分数越高）</span></span><br><span class="line">        time_diff = current_time - item[<span class="string">&quot;timestamp&quot;</span>]</span><br><span class="line">        time_score = <span class="built_in">max</span>(<span class="number">0</span>, <span class="number">1</span> - time_diff / <span class="number">3600</span>)  <span class="comment"># 1小时内完全保留，之后衰减</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 结合其他因素（如对话长度、重要性标记等）</span></span><br><span class="line">        total_score = time_score</span><br><span class="line"></span><br><span class="line">        scored_items.append((total_score, item))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按分数排序，选择分数最高的</span></span><br><span class="line">    scored_items.sort(key=<span class="keyword">lambda</span> x: x[<span class="number">0</span>], reverse=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> [item <span class="keyword">for</span> score, item <span class="keyword">in</span> scored_items[:max_items]]</span><br></pre></td></tr></table></figure><h4 id="2-基于相关性的选择"><a href="#2-基于相关性的选择" class="headerlink" title="2. 基于相关性的选择"></a>2. 基于相关性的选择</h4><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">relevance_based_selection</span>(<span class="params">history, current_query, embedding_model, max_items=<span class="number">5</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;基于与当前查询相关性的选择&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> history:</span><br><span class="line">        <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 计算当前查询的向量</span></span><br><span class="line">    query_embedding = embedding_model.encode(current_query)</span><br><span class="line"></span><br><span class="line">    scored_items = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> item <span class="keyword">in</span> history:</span><br><span class="line">        <span class="comment"># 将历史对话内容转换为向量</span></span><br><span class="line">        content = item[<span class="string">&quot;user&quot;</span>] + <span class="string">&quot; &quot;</span> + item[<span class="string">&quot;assistant&quot;</span>]</span><br><span class="line">        content_embedding = embedding_model.encode(content)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 计算余弦相似度</span></span><br><span class="line">        similarity = cosine_similarity([query_embedding], [content_embedding])[<span class="number">0</span>][<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">        scored_items.append((similarity, item))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按相似度排序，选择最相关的</span></span><br><span class="line">    scored_items.sort(key=<span class="keyword">lambda</span> x: x[<span class="number">0</span>], reverse=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> [item <span class="keyword">for</span> similarity, item <span class="keyword">in</span> scored_items[:max_items]]</span><br></pre></td></tr></table></figure><h4 id="3-混合选择策略"><a href="#3-混合选择策略" class="headerlink" title="3. 混合选择策略"></a>3. 混合选择策略</h4><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SmartHistorySelector</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;智能历史选择器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, embedding_model=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.embedding_model = embedding_model</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">select_history</span>(<span class="params">self, history, current_query, max_context_length=<span class="number">3000</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;智能选择历史对话&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        selected = []</span><br><span class="line">        current_length = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 策略1：总是包含最近的一轮对话</span></span><br><span class="line">        <span class="keyword">if</span> history:</span><br><span class="line">            recent = history[-<span class="number">1</span>]</span><br><span class="line">            recent_length = <span class="variable language_">self</span>.estimate_length(recent)</span><br><span class="line">            <span class="keyword">if</span> recent_length &lt;= max_context_length:</span><br><span class="line">                selected.append(recent)</span><br><span class="line">                current_length += recent_length</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 策略2：基于相关性的选择</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.embedding_model <span class="keyword">and</span> <span class="built_in">len</span>(history) &gt; <span class="number">1</span>:</span><br><span class="line">            relevant = <span class="variable language_">self</span>.select_by_relevance(history[:-<span class="number">1</span>], current_query, <span class="number">3</span>)</span><br><span class="line">            <span class="keyword">for</span> item <span class="keyword">in</span> relevant:</span><br><span class="line">                item_length = <span class="variable language_">self</span>.estimate_length(item)</span><br><span class="line">                <span class="keyword">if</span> current_length + item_length &lt;= max_context_length:</span><br><span class="line">                    selected.append(item)</span><br><span class="line">                    current_length += item_length</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 策略3：如果还有空间，按时间顺序添加</span></span><br><span class="line">        remaining_space = max_context_length - current_length</span><br><span class="line">        <span class="keyword">if</span> remaining_space &gt; <span class="number">100</span>:  <span class="comment"># 至少100token的空间</span></span><br><span class="line">            <span class="keyword">for</span> item <span class="keyword">in</span> history:</span><br><span class="line">                <span class="keyword">if</span> item <span class="keyword">not</span> <span class="keyword">in</span> selected:</span><br><span class="line">                    item_length = <span class="variable language_">self</span>.estimate_length(item)</span><br><span class="line">                    <span class="keyword">if</span> item_length &lt;= remaining_space:</span><br><span class="line">                        selected.append(item)</span><br><span class="line">                        remaining_space -= item_length</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 按时间顺序排序</span></span><br><span class="line">        selected.sort(key=<span class="keyword">lambda</span> x: x[<span class="string">&quot;timestamp&quot;</span>])</span><br><span class="line">        <span class="keyword">return</span> selected</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">select_by_relevance</span>(<span class="params">self, history, query, max_items</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;基于相关性选择&quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># 简化实现，实际应使用向量相似度</span></span><br><span class="line">        query_lower = query.lower()</span><br><span class="line">        scored = []</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> item <span class="keyword">in</span> history:</span><br><span class="line">            content = (item[<span class="string">&quot;user&quot;</span>] + <span class="string">&quot; &quot;</span> + item[<span class="string">&quot;assistant&quot;</span>]).lower()</span><br><span class="line">            <span class="comment"># 简单关键词匹配</span></span><br><span class="line">            score = <span class="built_in">sum</span>(<span class="number">1</span> <span class="keyword">for</span> word <span class="keyword">in</span> query_lower.split() <span class="keyword">if</span> word <span class="keyword">in</span> content)</span><br><span class="line">            scored.append((score, item))</span><br><span class="line"></span><br><span class="line">        scored.sort(key=<span class="keyword">lambda</span> x: x[<span class="number">0</span>], reverse=<span class="literal">True</span>)</span><br><span class="line">        <span class="keyword">return</span> [item <span class="keyword">for</span> score, item <span class="keyword">in</span> scored[:max_items]]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">estimate_length</span>(<span class="params">self, exchange</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;估计对话长度&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">len</span>(exchange[<span class="string">&quot;user&quot;</span>]) + <span class="built_in">len</span>(exchange[<span class="string">&quot;assistant&quot;</span>])</span><br></pre></td></tr></table></figure><h3 id="记忆压缩与总结策略"><a href="#记忆压缩与总结策略" class="headerlink" title="记忆压缩与总结策略"></a>记忆压缩与总结策略</h3><p>随着对话的进行，记忆会不断累积。为了避免信息过载和减少资源消耗，需要定期对记忆进行压缩和总结。</p><h3 id="记忆压缩策略"><a href="#记忆压缩策略" class="headerlink" title="记忆压缩策略"></a>记忆压缩策略</h3><h4 id="1-基于重要性的压缩"><a href="#1-基于重要性的压缩" class="headerlink" title="1. 基于重要性的压缩"></a>1. 基于重要性的压缩</h4><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ImportanceBasedCompressor</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;基于重要性的记忆压缩器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, llm_client</span>):</span><br><span class="line">        <span class="variable language_">self</span>.llm = llm_client</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">compress_memories</span>(<span class="params">self, memories, target_ratio=<span class="number">0.5</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;压缩记忆，保留重要内容&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(memories) &lt;= <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> memories  <span class="comment"># 记忆太少，不需要压缩</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 评估每个记忆的重要性</span></span><br><span class="line">        scored_memories = []</span><br><span class="line">        <span class="keyword">for</span> memory <span class="keyword">in</span> memories:</span><br><span class="line">            importance = <span class="variable language_">self</span>.evaluate_importance(memory)</span><br><span class="line">            scored_memories.append((importance, memory))</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 按重要性排序</span></span><br><span class="line">        scored_memories.sort(key=<span class="keyword">lambda</span> x: x[<span class="number">0</span>], reverse=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 保留最重要的部分</span></span><br><span class="line">        keep_count = <span class="built_in">max</span>(<span class="number">1</span>, <span class="built_in">int</span>(<span class="built_in">len</span>(memories) * target_ratio))</span><br><span class="line">        compressed_memories = [memory <span class="keyword">for</span> _, memory <span class="keyword">in</span> scored_memories[:keep_count]]</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> compressed_memories</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">evaluate_importance</span>(<span class="params">self, memory</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;评估记忆的重要性&quot;&quot;&quot;</span></span><br><span class="line">        content = memory[<span class="string">&quot;content&quot;</span>]</span><br><span class="line">        metadata = memory.get(<span class="string">&quot;metadata&quot;</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 基于规则的重要性评估</span></span><br><span class="line">        importance_score = <span class="number">0.0</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 1. 基于类型</span></span><br><span class="line">        memory_type = metadata.get(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> memory_type == <span class="string">&quot;personal_info&quot;</span>:</span><br><span class="line">            importance_score += <span class="number">0.8</span></span><br><span class="line">        <span class="keyword">elif</span> memory_type == <span class="string">&quot;preference&quot;</span>:</span><br><span class="line">            importance_score += <span class="number">0.7</span></span><br><span class="line">        <span class="keyword">elif</span> memory_type == <span class="string">&quot;fact&quot;</span>:</span><br><span class="line">            importance_score += <span class="number">0.5</span></span><br><span class="line">        <span class="keyword">elif</span> memory_type == <span class="string">&quot;conversation&quot;</span>:</span><br><span class="line">            importance_score += <span class="number">0.3</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 2. 基于长度（适中的长度可能更重要）</span></span><br><span class="line">        content_length = <span class="built_in">len</span>(content)</span><br><span class="line">        <span class="keyword">if</span> <span class="number">50</span> &lt;= content_length &lt;= <span class="number">500</span>:</span><br><span class="line">            importance_score += <span class="number">0.2</span></span><br><span class="line">        <span class="keyword">elif</span> content_length &gt; <span class="number">500</span>:</span><br><span class="line">            importance_score += <span class="number">0.1</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 3. 基于时间衰减（越新的记忆越重要）</span></span><br><span class="line">        timestamp = metadata.get(<span class="string">&quot;timestamp&quot;</span>, <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">if</span> timestamp &gt; <span class="number">0</span>:</span><br><span class="line">            age_days = (time.time() - timestamp) / (<span class="number">24</span> * <span class="number">3600</span>)</span><br><span class="line">            recency_score = <span class="built_in">max</span>(<span class="number">0</span>, <span class="number">1</span> - age_days / <span class="number">30</span>)  <span class="comment"># 30天内线性衰减</span></span><br><span class="line">            importance_score += recency_score * <span class="number">0.5</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 4. 基于显式重要性标记</span></span><br><span class="line">        explicit_importance = metadata.get(<span class="string">&quot;importance&quot;</span>, <span class="number">0.5</span>)</span><br><span class="line">        importance_score += explicit_importance * <span class="number">0.5</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">min</span>(importance_score, <span class="number">1.0</span>)  <span class="comment"># 归一化到0-1</span></span><br></pre></td></tr></table></figure><h4 id="2-基于聚类的压缩"><a href="#2-基于聚类的压缩" class="headerlink" title="2. 基于聚类的压缩"></a>2. 基于聚类的压缩</h4><p>将相似记忆聚类，然后总结每个聚类：</p><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn.cluster <span class="keyword">import</span> KMeans</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ClusterBasedCompressor</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;基于聚类的记忆压缩器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, embedding_model</span>):</span><br><span class="line">        <span class="variable language_">self</span>.embedding_model = embedding_model</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">compress_by_clustering</span>(<span class="params">self, memories, n_clusters=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;通过聚类压缩记忆&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(memories) &lt;= <span class="number">3</span>:</span><br><span class="line">            <span class="keyword">return</span> memories  <span class="comment"># 记忆太少，不需要聚类</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 确定聚类数量</span></span><br><span class="line">        <span class="keyword">if</span> n_clusters <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            n_clusters = <span class="built_in">min</span>(<span class="number">3</span>, <span class="built_in">len</span>(memories) // <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 获取所有记忆的embedding</span></span><br><span class="line">        embeddings = []</span><br><span class="line">        <span class="keyword">for</span> memory <span class="keyword">in</span> memories:</span><br><span class="line">            embedding = <span class="variable language_">self</span>.embedding_model.encode(memory[<span class="string">&quot;content&quot;</span>])</span><br><span class="line">            embeddings.append(embedding)</span><br><span class="line"></span><br><span class="line">        embeddings = np.array(embeddings)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 执行K-means聚类</span></span><br><span class="line">        kmeans = KMeans(n_clusters=n_clusters, random_state=<span class="number">42</span>, n_init=<span class="number">10</span>)</span><br><span class="line">        labels = kmeans.fit_predict(embeddings)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 对每个聚类进行总结</span></span><br><span class="line">        compressed_memories = []</span><br><span class="line">        <span class="keyword">for</span> cluster_id <span class="keyword">in</span> <span class="built_in">range</span>(n_clusters):</span><br><span class="line">            <span class="comment"># 获取该聚类的所有记忆</span></span><br><span class="line">            cluster_memories = [</span><br><span class="line">                memories[i] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(memories)) <span class="keyword">if</span> labels[i] == cluster_id</span><br><span class="line">            ]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> cluster_memories:</span><br><span class="line">                <span class="comment"># 总结该聚类的记忆</span></span><br><span class="line">                summary = <span class="variable language_">self</span>.summarize_cluster(cluster_memories)</span><br><span class="line">                compressed_memories.append(summary)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> compressed_memories</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">summarize_cluster</span>(<span class="params">self, cluster_memories</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;总结一个聚类的记忆&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(cluster_memories) == <span class="number">1</span>:</span><br><span class="line">            <span class="comment"># 只有一个记忆，直接返回</span></span><br><span class="line">            <span class="keyword">return</span> cluster_memories[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 合并所有记忆内容</span></span><br><span class="line">        all_content = <span class="string">&quot;\n&quot;</span>.join([m[<span class="string">&quot;content&quot;</span>] <span class="keyword">for</span> m <span class="keyword">in</span> cluster_memories])</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 使用LLM生成总结（这里简化实现）</span></span><br><span class="line">        <span class="comment"># 实际应调用LLM生成高质量的总结</span></span><br><span class="line">        summary_content = <span class="string">f&quot;相关主题的<span class="subst">&#123;<span class="built_in">len</span>(cluster_memories)&#125;</span>条记忆摘要：<span class="subst">&#123;all_content[:<span class="number">500</span>]&#125;</span>...&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 合并元数据</span></span><br><span class="line">        merged_metadata = &#123;</span><br><span class="line">            <span class="string">&quot;type&quot;</span>: <span class="string">&quot;cluster_summary&quot;</span>,</span><br><span class="line">            <span class="string">&quot;original_count&quot;</span>: <span class="built_in">len</span>(cluster_memories),</span><br><span class="line">            <span class="string">&quot;compressed&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">            <span class="string">&quot;timestamp&quot;</span>: time.time()</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: summary_content,</span><br><span class="line">            <span class="string">&quot;metadata&quot;</span>: merged_metadata</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><h4 id="3-增量总结策略"><a href="#3-增量总结策略" class="headerlink" title="3. 增量总结策略"></a>3. 增量总结策略</h4><p>在对话过程中逐步总结，而不是一次性处理所有历史：</p><figure class="highlight python"><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><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">IncrementalSummarizer</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;增量总结器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, llm_client, summary_interval=<span class="number">5</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.llm = llm_client</span><br><span class="line">        <span class="variable language_">self</span>.summary_interval = summary_interval  <span class="comment"># 每多少轮对话总结一次</span></span><br><span class="line">        <span class="variable language_">self</span>.conversation_buffer = []</span><br><span class="line">        <span class="variable language_">self</span>.summaries = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_conversation</span>(<span class="params">self, user_input, assistant_response</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;添加对话到缓冲区&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.conversation_buffer.append(&#123;</span><br><span class="line">            <span class="string">&quot;user&quot;</span>: user_input,</span><br><span class="line">            <span class="string">&quot;assistant&quot;</span>: assistant_response,</span><br><span class="line">            <span class="string">&quot;timestamp&quot;</span>: time.time()</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 检查是否需要总结</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(<span class="variable language_">self</span>.conversation_buffer) &gt;= <span class="variable language_">self</span>.summary_interval:</span><br><span class="line">            <span class="variable language_">self</span>.create_summary()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">create_summary</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;创建总结&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="variable language_">self</span>.conversation_buffer:</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 将对话缓冲区的所有内容总结为一段文字</span></span><br><span class="line">        conversation_text = <span class="string">&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> exchange <span class="keyword">in</span> <span class="variable language_">self</span>.conversation_buffer:</span><br><span class="line">            conversation_text += <span class="string">f&quot;用户: <span class="subst">&#123;exchange[<span class="string">&#x27;user&#x27;</span>]&#125;</span>\n&quot;</span></span><br><span class="line">            conversation_text += <span class="string">f&quot;助理: <span class="subst">&#123;exchange[<span class="string">&#x27;assistant&#x27;</span>]&#125;</span>\n\n&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 使用LLM生成总结</span></span><br><span class="line">        prompt = <span class="string">f&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">请将以下对话内容总结为一个简短的段落，保留核心信息和重要细节：</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">对话内容：</span></span><br><span class="line"><span class="string"><span class="subst">&#123;conversation_text&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">总结要求：</span></span><br><span class="line"><span class="string">1. 不超过200字</span></span><br><span class="line"><span class="string">2. 保留用户的主要需求和助理的关键回答</span></span><br><span class="line"><span class="string">3. 忽略问候语、重复内容和无关细节</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">总结：</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        summary = <span class="variable language_">self</span>.llm.generate(prompt, max_tokens=<span class="number">200</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 保存总结</span></span><br><span class="line">        <span class="variable language_">self</span>.summaries.append(&#123;</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: summary,</span><br><span class="line">            <span class="string">&quot;timestamp&quot;</span>: time.time(),</span><br><span class="line">            <span class="string">&quot;original_count&quot;</span>: <span class="built_in">len</span>(<span class="variable language_">self</span>.conversation_buffer)</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 清空缓冲区</span></span><br><span class="line">        <span class="variable language_">self</span>.conversation_buffer.clear()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_context</span>(<span class="params">self, include_recent=<span class="literal">True</span>, include_summaries=<span class="literal">True</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;获取上下文&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        context_parts = []</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> include_summaries <span class="keyword">and</span> <span class="variable language_">self</span>.summaries:</span><br><span class="line">            <span class="comment"># 添加所有总结</span></span><br><span class="line">            <span class="keyword">for</span> i, summary <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="variable language_">self</span>.summaries[-<span class="number">3</span>:]):  <span class="comment"># 最多3个总结</span></span><br><span class="line">                context_parts.append(<span class="string">f&quot;[对话总结 <span class="subst">&#123;i+<span class="number">1</span>&#125;</span>]\n<span class="subst">&#123;summary[<span class="string">&#x27;content&#x27;</span>]&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> include_recent <span class="keyword">and</span> <span class="variable language_">self</span>.conversation_buffer:</span><br><span class="line">            <span class="comment"># 添加最近的对话</span></span><br><span class="line">            <span class="keyword">for</span> exchange <span class="keyword">in</span> <span class="variable language_">self</span>.conversation_buffer[-<span class="number">3</span>:]:  <span class="comment"># 最多3轮最近对话</span></span><br><span class="line">                context_parts.append(<span class="string">f&quot;用户: <span class="subst">&#123;exchange[<span class="string">&#x27;user&#x27;</span>]&#125;</span>&quot;</span>)</span><br><span class="line">                context_parts.append(<span class="string">f&quot;助理: <span class="subst">&#123;exchange[<span class="string">&#x27;assistant&#x27;</span>]&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;\n\n&quot;</span>.join(context_parts)</span><br></pre></td></tr></table></figure><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]>
    </content>
    <id>https://www.sekyoro.top/2026/01/12/RAG%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/</id>
    <link href="https://www.sekyoro.top/2026/01/12/RAG%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/"/>
    <published>2026-01-12T15:50:19.000Z</published>
    <summary>
      <![CDATA[<p>目前LLM应用集中在开发所谓AI智能体,理想中的AI智能体能够自主感知环境并作出决策,与环境的变化不断反馈,同时具备记忆,反思和工具能力.而目前的AI应用中,效果最好,成本也低的还是RAG.<br>]]>
    </summary>
    <title>RAG系统设计</title>
    <updated>2026-02-25T15:05:06.505Z</updated>
  </entry>
</feed>
