<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[metaの小屋]]></title><description><![CDATA[欢迎喵(≧∇≦)ﾉ]]></description><link>https://blog.teslongxiao.cn</link><image><url>https://blog.teslongxiao.cn/xxxxx.svg</url><title>metaの小屋</title><link>https://blog.teslongxiao.cn</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Thu, 23 Apr 2026 20:47:17 GMT</lastBuildDate><atom:link href="https://blog.teslongxiao.cn/feed" rel="self" type="application/rss+xml"/><pubDate>Thu, 23 Apr 2026 20:47:17 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[MagCam]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://blog.teslongxiao.cn/posts/default/20260412">https://blog.teslongxiao.cn/posts/default/20260412</a></p>]]></description><link>https://blog.teslongxiao.cn/posts/default/20260412</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/default/20260412</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Mon, 13 Apr 2026 16:10:23 GMT</pubDate></item><item><title><![CDATA[Trie字典树学习]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/default/20260331">https://blog.teslongxiao.cn/posts/default/20260331</a></blockquote><blockquote><p>之前在小羊肖恩杯上自己推到出来了trie的字符串方法，但是赛后没仔细刷类似的题目，导致校赛01字典树板题都没能过，明天集训队又有训练，遂补习。</p><h1 id="">推导</h1></blockquote><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/default/20260331#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/default/20260331</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/default/20260331</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Tue, 31 Mar 2026 08:52:59 GMT</pubDate></item><item><title><![CDATA[2026年校赛D题题解]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/default/20260328">https://blog.teslongxiao.cn/posts/default/20260328</a></blockquote><div><h2 id="">题目大意</h2><p>有函数 $S(n)$ 定义如下：</p><p>$$
S(n)=\sum<em>{i=0}^{m-1} (-1)^i d</em>i^2=d<em>0^2-d</em>1^2+d<em>2^2-d</em>3^2+\cdots + (-1)^{m-1} d_{m-1}^2
$$</p><p>这里序列 ${d<em>i}$ 是正整数 $n \in [1,2\times 10^6]$ 的所有真约数（去重）的升序排列，求 $\sum</em>{i=1}^n S(i)$</p><h2 id="">思路</h2><p>我们先用样例 $n=6$ 打表看看有什么规律：</p>
<table><thead><tr><th style="text-align:center"> $n$  </th><th style="text-align:center">          ${(-1)^id_i^2}$           </th><th style="text-align:center"> $S(n)$ </th></tr></thead><tbody><tr><td style="text-align:center"> $1$  </td><td style="text-align:center">               $1^2$                </td><td style="text-align:center">  $1$   </td></tr><tr><td style="text-align:center"> $2$  </td><td style="text-align:center">          $1^2\ , \ -2^2$           </td><td style="text-align:center">  $-3$  </td></tr><tr><td style="text-align:center"> $3$  </td><td style="text-align:center">           $1^2\ , \ 3^2$           </td><td style="text-align:center">  $-8$  </td></tr><tr><td style="text-align:center"> $4$  </td><td style="text-align:center">      $1^2\ , \ -2^2\ , \ 4^2$      </td><td style="text-align:center">  $13$  </td></tr><tr><td style="text-align:center"> $5$  </td><td style="text-align:center">          $1^2\ , \ -5^2$           </td><td style="text-align:center"> $-24$  </td></tr><tr><td style="text-align:center"> $6$  </td><td style="text-align:center"> $1^2\ , \ -2^2\ , \ 3^2\ , \ -6^2$ </td><td style="text-align:center"> $-30$  </td></tr></tbody></table><p>我们发现，其求和可以变成，</p><p>$$
\begin{aligned}
\sum_{i=1}^6 S(i)&amp;= 1^2+(1^2-2^2)+(1^2-3^2)+(1^2-2^2+4^2)+(1^2-5^2)+(1^2-2^2+3^2-6^2)\
&amp;= 1^2\times 6 + (-2^2-2^2-2^2)+(-3^2+3^2)+4^2+(-5^2)+(-6^2)\
\end{aligned}
$$</p><p>不难发现对于某个因子 $d$ 对于整体的贡献，就是其对于某个数 $x\in[1,n]$ 的升序排序的次序。例如因子 $2$ ，其整除的数有 ${2,4,6}$  在这三个数的因子排序都是第2个，所以其贡献是 $-2^2\times 3$。</p><p>那么我们可以根据这个发现，先枚举 $[1,n]$ 的所有因子 $d$ ，然后枚举其倍数 ${d,2d,3d,\cdots | xd \le n}$ ，作一个计数器用于记录其出现的次数，然后根据其出现的次数作贡献即可。</p><blockquote><p>即使用埃氏筛的思想，枚举倍数</p></blockquote><blockquote>
<p>[!NOTE]</p><p>这里还可以思考下是否存在 $O(n)$ 的线筛做法，我的思路是把每个数作质因数分解，看看是否存在递推（或者化归） $S(n)=S(p<em>1)S(p</em>2)\cdots S(p<em>k)$ 或者 $S(n)=aS(p</em>1)+bS(p<em>2)\cdots +cS(p</em>k)$ 的性质，然后就可以预处理质因数的 $S(n)$ 然后递推了。</p><p>但是我代几个数发现似乎并不存在这样的性质，所以这里就不展开了。</p></blockquote>
<h2 id="">代码</h2><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">using ll = long long;

void sol()
{
    ll n; scanf(&quot;%lld&quot;,&amp;n);
    ll s=n;  // 先把1的贡献记上
    int cnt[n+1];  //当然还可以使用bool省空间
    memset(cnt,0,sizeof(cnt));
    for(ll i=2; i&lt;=n; i++)
    {
        ll t=i*i;
        s+=(cnt[i]&amp;1? -t:t);
        cnt[i]++;
        for(ll j=2*i; j&lt;=n; j+=i)
        {
            s+=(cnt[j]&amp;1? -t:t);
            cnt[j]++;
        }
    }
    printf(&quot;%lld&quot;,s);
        
    return;
}
</code></pre>
<p>这里外循环 $n$ 次，然后内循环每个 $i$ 执行 $\lfloor \frac{n}{i} \rfloor$ 次 ，总的次数是，</p><p>$$
\begin{aligned}
T(n)&amp;=\sum<em>{i=1}^n \lfloor \frac{n}{i} \rfloor\
&amp;=n\sum</em>{i=1}^n \frac{1}{i}\
&amp;\le n \int_{1}^{n} \frac{1}{x} dx =n\ln n
\end{aligned}
$$</p><p>即时间复杂度是 $O(n\log n)$ 在2e6下还是可以过的</p>
<h2 id="">最后</h2><p>我们知道了枚举倍数的筛思想，下面引出埃氏筛（筛质数）：</p><p>埃氏筛是一种用于筛选质数的算法，其思路是：如果一个数 $n$ 是质数，那么它的倍数 $2n,3n,4n,\cdots$ 一定不是质数。因此，我们可以从 $2$ 开始，依次标记每个质数的倍数，最后剩下的数就是质数。</p><p>由于埃氏筛只对质数进行倍数枚举，其时间复杂度是 $O(n\log \log n)$</p><blockquote>
<p>[!TIP]</p><p>类似的题目:</p><ul><li><a href="https://www.luogu.com.cn/problem/P14073">[GESP202509 五级] 数字选取</a> <del>筛质数模板题（大雾</del></li><li><a href="https://ac.nowcoder.com/acm/problem/229811">Bitwise Or vs LCM</a> <del>这里要点放缩的小巧思</del></li><li><a href="https://www.luogu.com.cn/problem/P12677">[Brooklyn Round 1 &amp; NNOI Round 1 A] Flying Flower</a> <del>找到最优策略就好</del></li></ul></blockquote></div><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/default/20260328#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/default/20260328</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/default/20260328</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Sat, 28 Mar 2026 08:43:37 GMT</pubDate></item><item><title><![CDATA[基于ABC446E的启发探索]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/Tutorial/20260319">https://blog.teslongxiao.cn/posts/Tutorial/20260319</a></blockquote><div><blockquote><p>前言：刷题时意外碰到这题，初见时以为是简单的数论题，求出通项公式再分析即可，但实际又由于出现根号问题无法直接分析。求助好友后才发现是图论题，觉得这种建图方式很有趣，故记录下来，并延申。</p></blockquote>
<h1 id="">题意</h1><p><a href="https://atcoder.jp/contests/abc446/tasks/abc446_e">ABC446E</a></p><p>题意就是对于一个序列有：</p><p>$$
s<em>1=x \
s</em>2=y \
s<em>{i}= As</em>{i-1} + Bs_{i-2} \ , \ i \geq 3
$$</p><p>求对于集合 $0\le x \ , \ y \le M-1$ ，x和y不同取值下，无限长的序列中不存在 $M \mid s<em>i$ （即 $s</em>i$ 不被 M 整除）的(x,y)取值个数。</p><h1 id="">思路</h1><p>首先这个通项公式很好写出，就是：</p><p>$$
s<em>i=c</em>1 \lambda<em>1^i+c</em>2 \lambda_2^i
$$</p><p>其中 $\lambda<em>1 \ , \ \lambda</em>2$ 是方程 $x^2-Ax-B=0$ 的根， $c<em>1 \ , \  c</em>2$ 是由 $s<em>1=x$ 和 $s</em>2=y$ 决定的常数。</p><p>但是很显然是这个底数 $\lambda$ 不一定是整数，比如著名的斐波那契数列，其底数就是带有根号的黄金分割数，直接分析还需要考虑用二项定理展开，然后根据具体的 $c<em>1$ 和 $c</em>2$ 的值，消去根号才能分析，实属复杂。</p><p>注意到这里的模数M只有1000，尝试暴力会发现，我们需要分别枚举 $x$ 和 $y$ ，然后对于每个独立的(x,y)，计算在前M*M项数中是否存在<code>%M==0</code></p>
<pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">for(int i=0;i&lt;M;i++)for(int j=0;j&lt;M;j++)
{
    bool ok=true;
    vector&lt;int&gt; s(M*M+1);
    s[1]=i, s[2]=j;
    for(int k=3; k&lt;=M*M; k++)
    {
        s[k]=((ll)A*s[k-1]+(ll)B*s[k-2])%M;
        if(s[k]==0)
        {  
            ok=false;
            break;
        }
    }
    if(ok) ans++;
}
</code></pre>
<p>但是这样的复杂度是 $O(M^4)$ ，显然TLE。</p><h2 id="">图</h2><p>这里考虑所有的x和y的取值后，序列 ${s}$ 的值必然在模数 $M$ 内<br/>我们作状态 $(u,v):=(s[i-1],s[i])$ ，那么有转移 $(s<em>{i-2},s</em>{i-1}) \to (s<em>{i-1},s</em>i)$ ，也就是每个节点有且仅有一条出边（<strong>不一定只有一条入边</strong>），即外向基环树</p><p>那么不同对的(x,y)又应该如何考虑呢？</p><p>我们注意到对于 A=1, B=2;
我们取 $(x=0,y=1)$ 有</p><p>$$
(s<em>1=0,s</em>2=1) \ , \ (s<em>2=1, s</em>3=1+0=1) \ , \ (s<em>3=1, s</em>4=3)\cdots
$$</p><p>注意这个节点 $(1,1)$ ，如果我们取 $(x=1,y=1)$ 在数值上也可以得到这个结果 $(s[1]=1,[2]=1)$<br/>也就是说，对于该空间 $M^2$ 内，节点的数值既可以表示 $(x,y)$ 对，同时也是 $(s[i-1],s[i])$</p><p>对于该空间 $M^2$ 具体的节点是：</p><p>$$
\begin{array}{ccccc}
(0,0) &amp; (0,1) &amp; (0,2) &amp; \cdots &amp; (0,M-1) \
(1,0) &amp; (1,1) &amp; (1,2) &amp; \cdots &amp; (1,M-1) \
\vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \
(M-1,0) &amp; (M-1,1) &amp; (M-1,2) &amp; \cdots &amp; (M-1,M-1)
\end{array}
$$</p><p>当然其具体的连接关系为:</p><p>$$
(p \ ,\ q) \to (q\ ,\ Aq+Bp\pmod M)
$$</p><p>题目求有多少个(x,y)产生的序列不含M的倍数，在这个状态空间中也就是确定一个起点(x,y)沿着其出边一直走， 并使其不能碰到x=0或者y=0，统计这样的路径。但是这样直接遍历每个节点（上面的方法），复杂度还是 $O(M^4)$ ，显然会超。</p><p>这里说明下一些应该知道的关系</p><ul><li>$M^2=$ 经过含0节点的路径数 $+$ 没经过含0节点的路径数</li><li>对于所有的 $(x,y)$ 或 $(x,0)$ 或 $(0,0)$ 作为起点的序列必然会经过含0节点</li><li>如果一个节点的后继是含0节点 那么其前驱的后继的后继也是会经过含0节点</li></ul><p>那么必然地，</p><ul><li>对于从0节点出发，反着走，经过的每个节点所对应的序列，都会经过0节点
<ul><li>也就是从所有的0节点反着出发，经过的每个节点都视为必然有一条路径经过
<ul><li>即经过每个节点计数+1</li></ul></li></ul></li></ul><p>我们只要 $M^2$ 地遍历节点构造反图，然后从含0节点bfs搜索计数标记节点，然后 $M^2-\text{cnt}$ 即可</p><h1 id="">代码</h1><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">inline int encode(int x, int y, int m)
{
    return x*m+y;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int m,a,b; cin &gt;&gt; m &gt;&gt; a &gt;&gt; b;
    // 这里使用编码存(x,y) x*m+y
    vector&lt;vector&lt;int&gt; &gt; rg(m*m);
    for(int i=0; i&lt;m; i++)
    {
        for(int j=0; j&lt;m; j++)
        {
            int x1=i, y1=j;
            int x2=j, y2=((ll)a*j+(ll)b*i)%m;
            int u=encode(x1,y1,m);
            int v=encode(x2,y2,m);
            rg[v].push_back(u);
        }
    }
    vector&lt;bool&gt; vis(m*m,false);
    queue&lt;int&gt; q;
    
    for(int k=0; k&lt;m; k++)
    {
        int u1=encode(0,k,m);
        if(!vis[u1])
        {
            vis[u1]=true;
            q.push(u1);
        }
        int u2=encode(k,0,m);
        if(!vis[u2])
        {
            vis[u2]=true;
            q.push(u2);
        }
    }
    
    int cnt=0;
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        cnt++;
        for(int v:rg[u])if(!vis[v])
        {
            vis[v]=true;
            q.push(v);
        }
    }
    
    cout &lt;&lt; m*m-cnt &lt;&lt; endl;
    
    return 0;
}
</code></pre>
<h1 id="">总结</h1><p>本题很巧妙地使用了状态空间将序列的集合和(x,y)初始选点的集合合并起来，使得在搜索图（序列结果）中恰好也统计了选点的个数，很妙！</p><p>而且注意到每个状态到下一个状态是唯一的，即每个结点只有一条出边，整个图由若干个 <strong>基环树 <em>Functional Components</em></strong> 组成，这意味着从任何点出发，最终都会进入一个环。</p><h1 id="">扩展</h1><p>下面引出一些数论问题和图论问题的重合部分：</p><h2 id="">序列的周期与相遇问题</h2><p>给定 $x<em>{n+1} := (Ax</em>n + C) \pmod M$，求序列从何时开始进入循环，循环长度是多少？</p><h3 id="">数论解法</h3><p>该问题如果使用数论直接解的话十分复杂，这里只给出大概解法：</p><p>1) $C=0$ 时</p><p>如果 $C=0$ 即讨论 $x<em>n = A^n x</em>0 \pmod M$ 的周期和进入周期情况。</p><p>我们先把 $x<em>0$ 消掉，考虑同余式 $A^nx</em>0 \equiv A^{n+k}x<em>0 \pmod M$ ，那么同时除以其最小公倍数 $g=\gcd(x</em>0,M)$ 得到 ：</p><p>$$
A^n \frac{x<em>0}{g} \equiv A^{n+k} \frac{x</em>0}{g} \pmod {\frac{M}{g}}
$$</p><p>注意到 $\gcd(\frac{x<em>0}{g} ,\frac{M}{g})=1$ ，所以 $\frac{x</em>0}{g}$ 在模 $\frac{M}{g}$ 下有逆元，上式同时乘以 $\frac{x_0}{g}$ 的逆元，得到：</p><p>$$
A^n \equiv A^{n+k} \pmod {m} \ , \ (\ m=\frac{M}{g}\ ) \tag{*}
$$</p><p>然后考虑模m意义下对A幂的影响，提取出关键因子，考虑 $m$ 的质因数分解，设 $m=p<em>1^{q</em>1}p<em>2^{q</em>2}\cdots p<em>n^{q</em>n}$ ，并定：</p><p>$$
m<em>1=\prod</em>{1 \le i \le n, p<em>i | A} p</em>i^{q<em>i} \
m</em>2=\prod<em>{1 \le i \le n, p</em>i \nmid A} p<em>i^{q</em>i} \
$$</p><p>也就是将 $M$ 分解为含整除 $A$ 的质因数和不含整除 $A$ 的质因数，那么 $(*)$ 式成立当且仅当（CRT）：</p><p>$$
A^n \equiv A^{n+k} \pmod {m<em>1} \
A^n \equiv A^{n+k} \pmod {m</em>2} \
$$</p><ul><li><p>对于模 $m<em>2$ 的项， $\gcd(A,m</em>2)=1$ ，由欧拉定理有：</p><p>  $$ A^{\varphi(m<em>2)} \equiv 1 \pmod {m</em>2} $$</p><p>  即在对于这个模数来说，序列 $A \pmod{m<em>2}$ 是循环的，且最小正周期 $t$ 为 $A \pmod{m</em>2}$ 的阶，记作：</p><p>  $$ T=\delta<em>{m</em>2}(A) $$</p><p>  这里阶满足 $\delta<em>{m</em>2}(A) | \varphi(m<em>2)$ 即阶为 $m</em>2$ 的欧拉函数的约数，这样可以用程序快速求出。<br/>  例如先初始化阶等于其欧拉函数，尝试是否满足 $A^{\delta<em>{m</em>2}(A)} \equiv 1 \pmod {m<em>2}$ ，然后不断除以 $\varphi(m</em>2)$ 的每个质因子，直到不能满足余数为1，此时上个阶即为所求。</p></li><li><p>对于模 $m<em>1$ 的项，由于其所有素因子均能整除 $A$，设存在足够大的 $n$ 使得 $A^n$ 包含 $m</em>1$ 所需的所有素因子幂次。<br/>  即此时 $n$ 满足：</p><p>  $$ A^n \equiv 0 \pmod {m_1} $$</p><p>  那么对于之后的 $n+k \ , \ k&gt;1$ ， $A^{n+k} \pmod{m_1}$ 仍然满足上述条件。<br/>  我们找到其预周期也就是找到最小的 $t$，使得对于所有 $n \ge t$，都有：</p><p>  $$a^n \equiv a^{n+k} \pmod{M_1}$$</p><p>  我们记 $v<em>p(x)$ 为 $x$ 的 $p$ 进赋值（即x中质因子p的最高幂次），则对于 $A^n \equiv 0 \pmod{p^{v</em>p(m_1)}}$ 满足：</p><p>  $$n\cdot v<em>p(A) \ge v</em>p(m) \Rightarrow n \ge \left \lceil \frac{v<em>p(m)}{v</em>p(A)} \right \rceil$$</p><p>  因此，进入周期 $T$ 的最小预周期 $t$ 为：</p><p>  $$ t=\max<em>{p|m</em>1} \left \lceil \frac{v<em>p(m)}{v</em>p(A)} \right \rceil $$</p><p>  若 $m_1=1$ （即 $\gcd(A,m)=1$ ），则 $t=0$ ，这也印证了当A和m互质时，直接进入大循环。</p></li></ul><p>2) $C \ne 0$ 时</p><p>{% note primary no-icon %}</p><p><strong>引理</strong>：线性同余方程的齐次化 <em>Homogenization Lemma</em></p><p>给定线性同余递归序列 $x<em>{n+1} \equiv Ax</em>n + C \pmod M$ ，若满足 $\gcd(A-1, M) = 1$ ，则该序列可以通过坐标平移 $y<em>n = x</em>n - x^*$ 完美等价于齐次序列（即 $C=0$ 的形式）：</p><p>$$
y<em>{n+1} \equiv Ay</em>n \pmod M
$$</p><p>其中 $x^*$ 为递归映射的不动点。</p><p><strong>证明</strong>:</p><p>我们需要找到一个常数 $x^<em>$ ，使得当 $x_n = x^</em>$ 时， $x_{n+1}$ 也等于 $x^*$ 。根据递推式：</p><p>$$
x^<em> \equiv Ax^</em> + C \pmod M
$$</p><p>整理得：</p><p>$$
(1-A)x^* \equiv C \pmod M
$$</p><p>又 $\gcd(1-A, M) =\gcd(A-1, M)  = 1$ ，那么 $(1-A)$ 在模M下有逆元，即：</p><p>$$
x^* \equiv C(1-A)^{-1} \pmod M
$$</p><p>那么将坐标平移 $y<em>n = x</em>n - x^*$ ，并带入原式，得：</p><p>$$
\begin{aligned}
&amp; y<em>{n+1}+x^* \equiv A(y</em>n+x^<em>) + C \pmod M \
\Rightarrow &amp; y_{n+1}+x^</em> \equiv Ay<em>n + (Ax^*+C) \pmod M \
\Rightarrow &amp; y</em>{n+1}+x^<em> \equiv Ay_n + x^</em> \pmod M \ , \ \because \ Ax^<em>+C \equiv x^</em> \pmod M \
\Rightarrow &amp; y<em>{n+1} \equiv Ay</em>n \pmod M \ , \ \text{Subtract x* from both sides}
\end{aligned}
$$</p><p>也就是只要找到 $x^*$ ，就可以将原递推式等价于齐次递推式，从而利用上述方法求解。</p><p>{% endnote %}</p><p>由引理，可得如果 $\gcd(A-1, M) = 1$ 那么我们直接将递推式齐次化（也就是使得C=0），然后化归为前面的问题，求解即可。</p><p>但是如果 $\gcd(A-1, M) \ne 1$ ，即 $(A-1)$ 没有逆元，我们可以尝试扩大模数，令 $z<em>n:=(A-1)x</em>n+C$ ，那么 $z_{n+1}$ 可以表示为：</p><p>$$
\begin{aligned}
z<em>{n+1}&amp;=(A-1)x</em>{n+1}+C \
&amp;=(A-1)(Ax<em>n+C)+C \
&amp;=A(A-1)x</em>n+AC \
&amp;=A\left [(A-1)x<em>n+C \right ] \
&amp;=Az</em>n<br/>\end{aligned}
$$</p><p>神奇地，$z_{n}$ 的递推关系竟然是齐次的，由于z相对于x被扩大了(A-1)，那么其模数也应该扩大到 $M(A-1)$ :</p><p>$$
z<em>{n+1} \equiv Az</em>n \pmod {M(A-1)} \ , \ z<em>0=(A-1)x</em>0+C
$$</p><p>这与前面齐次的解法一样。</p>
<h3 id="">图论解法</h3><p>这个问题就很显然了，我们直接作状态空间 $x<em>n$ 由于是模M的，所以 $x</em>n={ 0,1,2\cdots M-1 }$ ，其连接关系就是 $x<em>{n+1} = (Ax</em>n + C) \pmod M$ 。<br/>我们要求的是进入周期的步长 $t$ ，即从 $x_0=C \pmod M$ 出发，第一次到达环的步长。然后环的长度就是周期 $T$ 。</p><h5 id=""><strong>程序</strong></h5><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">using ll = long long;

ll A,C,M;

inline ll f(ll x)
{
    return (A*x+C)%M;
}

signed main()
{
    cin &gt;&gt; A &gt;&gt; C &gt;&gt; M;
    // floyd
    ll sl=f(0);
    ll fa=f(sx); // 快慢指针
    while(sl!=fa)
    {
        sl=f(sl);
        fa=f(f(fa));
    }
    // sx,nx都在环上相遇
    // 且i,j到环的入口距离相同
    ll t=0; // 预周期
    ll i=f(0), j=sl;
    // 让i处于x0的位置，j在环入口，不断移动直到相遇
    while(i!=j)
    {
        i=f(i);
        j=f(j);
        t++;
    }
    ll T=1; // 周期
    j=f(i);  // j从环入口下一个结点开始
    while(i!=j) j=f(j), T++;
    
    cout &lt;&lt; &quot;pre period:&quot; &lt;&lt; t &lt;&lt; endl;
    cout &lt;&lt; &quot;period:&quot; &lt;&lt; T &lt;&lt; endl;

    return 0;
}
</code></pre>
<p>当然还有直接使用dsu的解法：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">using ll = long long;

ll A, C, M;

inline ll f(ll x)
{
    return (A * x + C) % M;
}

signed main()
{
    cin &gt;&gt; A &gt;&gt; C &gt;&gt; M;
    vector&lt;ll&gt; dsu(M, -1);
    ll x=f(0), s=0;
    while(1)
    {
        if(dsu[x]!=-1)
        {
            ll t=dsu[x];     // 预周期
            ll T=s-dsu[x];   // 周期

            cout &lt;&lt; t &lt;&lt; &quot; &quot; &lt;&lt; T &lt;&lt; endl;
            break;
        }

        dsu[x]=s;
        x=f(x);
        s++;
    }

    return 0;
}
</code></pre>
<p>这里解释下这个简洁的dsu为什么可以完成：</p><p>我们假设有这样一个序列，</p><pre class=""><code class="">                            t                             s
x[0] → x[1] → x[2] → … → x[t-1] → x[t] → x[t+1] → … → x[t+T-1]
                              ↑__________________________|
                                         T = s-t
</code></pre>
<p>这里在<code>x[t-1]</code>进入环，那么dsu从x[0]开始计数，数到<code>x[t+T-1]</code>时计数步数为s，由<code>x=f(x)</code>进入<code>x[t-1]</code>这个已经被dsu标记过，那么此时<code>dsu[t-1]</code>的计数步数就是预周期t，s-t就是周期。</p>
<h2 id="">质因数分解</h2><p>对于质因数分解，我们最直接就是使用试除法，枚举不大于 $\sqrt{m}$ 的质因子，然后不断除，直到不能整除为止，但是时间复杂度是 $O(\sqrt{m})$ ，对于大数来说显然是不够的。</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">vector&lt;int&gt; p,ep;
void f(int x)
{
    for(int p=2; p*p&lt;=n; p++)if(n%p==0)
    {
        int c=0;
        while(n%p==0) n/=p,c++;
        p.push_back(p), ep.push_back(c);
    }
    if(n&gt;1) p.push_back(n), ep.push_back(1);
}
</code></pre>
<h3 id="">数论解法</h3><p>我们考虑一个奇合数 $n$ 其能被分解成 $n=a\times b$ ，那么其必然是两个平方数的差：</p><p>$$
n=x^2-y^2=(x+y)(x-y)
$$</p><p>这个方法我们可以先让 $x:=\sqrt n $ ，不断向上枚举x，直到 $x^2-n$ 是个完全平方数为止，那么 $y=\sqrt{x^2-n}$ ，然后 $a=x+y$ ， $b=x-y$ ，就可以得到 $n$ 的一个因数分解。<br/>然后继续按照上面的逻辑分解a和b，直到分解到质数为止。</p><p>这种方法对于n的两个因子十分接近时，效率会很高。</p><p>但是反之，如果n的两个因子差距很大，我们需要枚举到很大的x才能找到这个完全平方数，我们可以考虑同余式：</p><p>$$
\begin{aligned}
&amp;x^2-y^2=n \
\Rightarrow &amp; x^2-y^2\equiv 0 \pmod n \
\Rightarrow &amp; x^2 \equiv y^2 \pmod n \
\end{aligned}
$$</p><p>那么我们只要找到 $n | x^2-y^2$ 即可，或者构造 $x^2 \equiv y^2 \pmod n$ 的解，比如随机或者构造平方。</p><p>我们考虑利用最大公约数提取因子，如果n整除 (x-y)(x+y) ，这并不代表n一定整除这其中某一个因子。而我们为了质因数分解，肯定不希望(x-y)或者(x+y)不是n的平凡因子（即因子不是1或者n本身），我们需要保证n的因子都分步在这两项中。</p><p>我们假设 $n = p \cdot q$（$p, q$ 为互异的质数），那么由CRT有：</p><p>$$
\begin{cases}
x^2 \equiv y^2 \pmod p \
x^2 \equiv y^2 \pmod q
\end{cases}
$$</p><p>因为 $p$ 是质数，那么 $\Z_p$ 是个域，二次方程 $x^2 - y^2 = 0$ 只有两个解（域上的多项式性质）：</p><p>$$
x \equiv y \pmod p \quad \text{OR} \quad x \equiv -y \pmod p
$$</p><p>同理，对于模 $q$ 也有：</p><p>$$
x \equiv y \pmod q \quad \text{OR} \quad x \equiv -y \pmod q
$$</p><p>也就是在模n的意义下这四个解：</p><ul><li>平凡解</li></ul><p>$$
\begin{cases}
x \equiv y \pmod p \
x \equiv y \pmod q \
\end{cases} \Rightarrow (x-y) \equiv 0 \pmod n \ 
\gcd(x-y, n)=n \text{ which we don&#x27;t want} \
\begin{cases}
x \equiv -y \pmod p \
x \equiv -y \pmod q \ 
\end{cases} \Rightarrow (x+y) \equiv 0 \pmod n \
\gcd(x+y, n)=n \text{ which we don&#x27;t want} \
$$</p><ul><li>非平凡解</li></ul><p>$$
\begin{cases}
x \equiv y \pmod p \
x \equiv -y \pmod q \
\end{cases} \Rightarrow x \not \equiv \pm y \pmod n \
\text{Thus } \gcd(x-y,n)=p \text{ which we want} \ 
\begin{cases}
x \equiv -y \pmod p \
x \equiv y \pmod q \
\end{cases} \Rightarrow x \not \equiv \pm y \pmod n \
\text{Thus } \gcd(x+y,n)=q \text{ which we want} \
$$</p><p>因此我们对于 $n | x^2-y^2$ 再加以非平凡解限定的条件 $x \not \equiv \pm y \pmod n$ ，此时n的一部分因子必然在左右两项都有分布。</p><p>具体的分解过程如下：</p><ul><li>寻找，找到一对 $(x, y)$ 使得 $x^2 \equiv y^2 \pmod n$</li><li>提取，计算 $g = \gcd(x-y, n)$</li><li>拆分， $n := g \times (\frac{n}{g})$。我们将 $a$ 和 $b = \frac{n}{a}$ 视为新的待分解数</li><li>终止，如果某个数通过 Miller-Rabin 等算法检测为质数，则停止对该分支的迭代</li></ul><p>即产生了一颗递归树——因子分解树。</p>
<h4 id=""><strong>程序</strong></h4><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">using ll = long long;

ll mul(ll a, ll b, ll mod)
{
    return (__int128)a * b % mod;
}

ll qpow(ll a, ll b, ll mod)
{
    ll res=1;
    while(b)
    {
        if(b&amp;1) res=mul(res, a, mod);
        a=mul(a, a, mod);
        b &gt;&gt;= 1;
    }
    return res;
}

bool MillerRabin(ll n)
{
    if (n &lt; 2) return false;
    for (ll p : {2, 3, 5, 7, 11, 13, 17, 19, 23})
    {
        if (n % p == 0) return n == p;
    }

    ll d = n - 1, s = 0;
    while ((d &amp; 1) == 0)
    {
        d &gt;&gt;= 1;
        s++;
    }

    auto check = [&amp;](ll a)
    {
        ll x = qpow(a, d, n);
        if (x == 1 || x == n - 1) return true;
        for (int i = 1; i &lt; s; i++)
        {
            x = mul(x, x, n);
            if (x == n - 1) return true;
        }
        return false;
    };

    for (ll a : {2, 325, 9375, 28178, 450775, 9780504, 1795265022})
    {
        if (a % n == 0) continue;
        if (!check(a)) return false;
    }

    return true;
}

ll PollardRho(ll n)
{
    if(~n&amp;1) return 2;
    while(1)
    {
        ll x=rand()%(n-2)+2;
        ll y=x;
        ll c=rand()%(n-1)+1;

        auto f=[&amp;](ll x)
        {
            return (mul(x, x, n)+c)%n;
        };

        ll d=1;

        while(d==1)
        {
            x=f(x);
            y=f(f(y));
            d=__gcd(abs(x - y), n);
        }
        if(d!=n) return d;
    }
}

map&lt;ll,int&gt; mp; // &lt;p,ep&gt;

// 质因数分解
void factor(ll n)
{
    if(n==1) return;

    if(MillerRabin(n))
    {
        mp[n]++;
        return;
    }

    // 用PollardRho分解
    ll d=PollardRho(n);
    factor(d);
    factor(n/d);
}
</code></pre>
<h3 id="">图论解法</h3></div><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/Tutorial/20260319#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/Tutorial/20260319</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/Tutorial/20260319</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Wed, 18 Mar 2026 17:03:16 GMT</pubDate></item><item><title><![CDATA[基于ABC448E的启发探索]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/Tutorial/20250308">https://blog.teslongxiao.cn/posts/Tutorial/20250308</a></blockquote><div><h1 id="">原题大意</h1><p>给出一个大数$N$其形式为：</p><p>$$
\overbrace{c<em>{k-1}; c</em>{k-1}; \cdots; c<em>{k-1}}^{l</em>{k-1}}\cdots\overbrace{c<em>1; c</em>1; \cdots; c<em>1}^{l</em>1}\overbrace{c<em>0; c</em>0; \cdots; c<em>0}^{l</em>0} \tag{*}
$$</p><p>其中 $c<em>i\in{0,1,\cdots,9} \ , \  1 \le l</em>i \le 10^9 , \ 1 \le k \le 10^5$</p><p>求 $\lfloor \frac{N}{M} \rfloor  \pmod {10007} $</p><h1 id="">思路</h1><p>我们考虑将向下取整符号去掉，</p><p>$$
\because \ n=q \cdot m+r \ (0 \le r &lt; m)  \tag{**} \
\therefore \ q=\frac{n-r}{m}=\frac{n-n\mod m}{m} \
$$</p><p>对于 $\text{ans}:=\lfloor \frac{n}{m} \rfloor  \pmod {M} $ 可以化成：</p><p>$$
\begin{aligned}
\text{ans} :&amp;= (n-n\mod m)\cdot \text{inv}(m) \pmod {M} \
&amp;=[\ (n \mod M - (n\mod m)\mod M +M) \mod M ]\cdot \text{inv}(m) \pmod {M} \
\end{aligned}
$$</p><p>我们记 $ \text{inv}(m) $ 为 $m$ 在模 $M$ 意义下的逆元 ，并令：</p><p>$$
r<em>m:=n \mod m \ r</em>M:=n \mod M
$$</p><p>又由 $(*)$ 式可知：</p><p>$$
\begin{aligned}
r<em>M&amp;=\overbrace{c</em>{k-1}; c<em>{k-1}; \cdots; c</em>{k-1}}^{l<em>{k-1}}\cdots\overbrace{c</em>1; c<em>1; \cdots; c</em>1}^{l<em>1}\overbrace{c</em>0; c<em>0; \cdots; c</em>0}^{l<em>0} \pmod {M} \
&amp;=\sum</em>{i=0}^{k-1} c<em>i 10^{l</em>0+l<em>1+\cdots+l</em>{i-1}}\sum<em>{j=0}^{l</em>i-1} 10^{j} \pmod {M} \ , \ \ \text{let} \ \ s<em>i=\sum</em>{j=0}^{i-1} l<em>j \
&amp;=\sum</em>{i=0}^{k-1}c<em>i10^{s</em>i}\sum<em>{j=0}^{l</em>i-1}10^j \pmod {M} \
&amp;=\sum<em>{i=0}^{k-1}10^{s</em>i}\cdot G(l_i,M) \pmod {M} \
\end{aligned}
$$</p><p>这里 $G(l<em>i,M)$ 表示 $c</em>i\cdot(10^0+10^1+\cdots+10^{l_i-1})\pmod M$<br/>可以使用几何级数的递推公式求出：</p><p>$$
G(n,M)=\begin{cases} 
G(l/2,M) \cdot (10^{l/2} + 1) \pmod M &amp; l \text{ is even} \ 
G(l-1) \cdot 10 + c_i \pmod M &amp; l \text{ is odd} \end{cases}
$$</p><p>同理：</p><p>$$
\begin{aligned}
r<em>m&amp;=\overbrace{c</em>{k-1}; c<em>{k-1}; \cdots; c</em>{k-1}}^{l<em>{k-1}}\cdots\overbrace{c</em>1; c<em>1; \cdots; c</em>1}^{l<em>1}\overbrace{c</em>0; c<em>0; \cdots; c</em>0}^{l<em>0} \pmod {m} \
&amp;=\sum</em>{i=0}^{k-1}c<em>i10^{s</em>i}\sum<em>{j=0}^{l</em>i-1}10^j \pmod {m} \
&amp;=\sum<em>{i=0}^{k-1}10^{s</em>i}\cdot G(l_i,m) \pmod {m} \
\end{aligned}
$$</p><p>然后将 $r<em>m$ 和 $r</em>M$ 改成递推的形式（从高位到低位），就可以在 $O(k)$ 的时间复杂度内求出答案。</p><p>$$
\begin{aligned}
r<em>m&amp;:=(r</em>m\cdot 10^{l<em>i}+G(l</em>i\ ,\ m)\ ) \pmod m \
r<em>M&amp;:=(r</em>M\cdot 10^{l<em>i}+G(l</em>i \ ,\ M)\ ) \pmod M \
\end{aligned}
$$</p><h1 id="">代码</h1><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">const ll M=10007;

ll qpow(ll a, ll b, ll m)
{
    ll res=1;
    a%=m;
    while(b)
    {
        if(b&amp;1)res=res*a%m;
        a=a*a%m;
        b&gt;&gt;=1;
    }
    return res;
}

ll inv(ll x)
{
    return qpow(x,M-2,M);
}

ll sum(ll l, ll m)
{
    if(l==0)return 0;
    if(l==1)return 1;
    if(~l&amp;1)return sum(l&gt;&gt;1, m)*(qpow(10, l&gt;&gt;1, m)+1)%m;
    return (sum(l-1,m)*10+1)%m;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    ll k,m; cin &gt;&gt; k &gt;&gt; m;
    vector&lt;pll&gt; A(k);
    for(int i=k-1; i&gt;=0; i--)
        cin &gt;&gt; A[i].first &gt;&gt; A[i].second;

    ll a=0, rm=0, p=1, pm=1;
    
    // 这里是从低位到高位递推的
    for(auto [c,l]:A)
    {
        ll ca=c*sum(l,M)%M;
        a=(a+ca*p)%M;
        p=p*qpow(10,l,M)%M;
        
        ll cm=c*sum(l,m)%m;
        rm=(rm+cm*pm)%m;
        pm=pm*qpow(10,l,m)%m;
    }
    
    ll ans=(a-(rm%M)+M)%M*inv(m)%M;
    cout&lt;&lt;ans;

    return 0;
}
</code></pre>
<h1 id="">扩展</h1><h2 id="m">M不是质数</h2><p>如果M不是质数，但 $gcd(m,M)≠1$ 那么逆元 $ \text{inv}(m) $ 可以用扩展欧几里得算法求出；<br/>但 $gcd(m,M)≠1$ 时，逆元根本不存在，这是 $(**)$ 式可以变形成：</p><p>$$
m \cdot q \equiv (r<em>M-r</em>m) \pmod M \tag{<em>*</em>}
$$</p><p>一个关于q的线性同余方程组，可以先提取公因子 $g=gcd(m,M)$ 归一化，</p><p>$$
\frac{m}{g} \equiv \frac{(r<em>M-r</em>m)}{g} \pmod {\frac{M}{g}}
$$</p><p>此时 $gcd(\frac{m}{g} , \frac{M}{g})=1$ ，就可以用扩展欧几里得算法求出特解，</p><p>$$
q<em>0 \equiv \frac{(r</em>M-r_m)}{g} \cdot \text{inv}(\frac{m}{g}) \pmod {\frac{M}{g}}
$$</p><p>在加上 $k$ 个 $\frac{M}{g}$ ，就可以得到通解：</p><p>$$
q=q_0+k \cdot \frac{M}{g} \ , \ k=0,1,2,\cdots, g-1
$$</p><p>但是 $k$ 是由 $n$ 唯一确定的，只通过 $r<em>m \ , \ r</em>M $ 而不计算 n/m 时是无法直接确定k的，应该换个思路。</p><p>显然这里 $gcd(n,M)&gt;1$ 对于周期为 $M$ 的 $r<em>M$ 以及周期为 $m$ 的 $r</em>m$ 来说，将周期扩大到其最小公倍数 $mM$ 。<br/>这样当n加上m时 $\lfloor n/m \rfloor$ 就增加1，继续增加到 $nM$ 后此时商就变成了M，在模M意义下又回到了0，当然是符合向下取整后取余的。<br/>具体地，我们设n对 $mM$ 的商为Q余数为R，那么有：</p><p>$$
n=Q(m\cdot M)+R \  , \  0 \le R &lt; mM
$$</p><p>我们要算的是 $\lfloor \frac{n}{m} \rfloor \pmod M$。将 $n$ 代入，得：</p><p>$$
\begin{aligned}
\lfloor \frac{n}{m} \rfloor \pmod M &amp;= \lfloor \frac{Q(m\cdot M)+R}{m} \rfloor \pmod M \
&amp;= \left(QM + \lfloor \frac{R}{m} \rfloor \right) \pmod M \
&amp;= 0+ \lfloor \frac{R}{m} \rfloor \pmod M \
&amp;=\frac{n \pmod {m \cdot M} }{m} \pmod M
\end{aligned}
$$</p><p>这样完美避开了逆元和k无法确定的问题。</p><h2 id="ml">m和l变大</h2><p>如果 $1 \le m,l \le 10^{18}$ ，最暴力的方法就是直接使用大数模拟完成，例如以下程序：</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">#include &lt;bits/stdc++.h&gt;
using namespace std;

using i128 = __int128;
using ll = long long;

i128 mul(i128 a, i128 b, i128 m)
{
    i128 res=0;
    while(b)
    {
        if(b&amp;1) res=(res+a)%m;
        a=(a+a)%m;
        b&gt;&gt;=1;
    }
    return res;
}

pair&lt;i128,i128&gt; calc(i128 l,i128 m)
{
    if(l==0) return {1%m,0};
    if(l==1) return {10%m,1%m};
    auto [p,s]=calc(l&gt;&gt;1,m);
    i128 p2=mul(p,p,m), s2=(mul(s,p,m)+s)%m;
    if(~l&amp;1) return {p2,s2};
    else return {mul(p2,10,m),(mul(s2,10,m)+1)%m};
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int k;ll m,M;
    cin &gt;&gt; k &gt;&gt; m &gt;&gt; M;

    i128 mM=(i128)m*M, cur=0;
    for(int i=0;i&lt;k;i++)
    {
        ll c,l; cin &gt;&gt; c &gt;&gt; l;
        auto [p,g]=calc(l,mM);
        i128 add=mul((i128)c,g,mM);
        cur=(mul(cur,p,mM)+add)%mM;
    }

    i128 ans=(cur/m)%M;
    cout&lt;&lt;(ll)ans;
}
</code></pre>
<p>但是很不幸，由于防止过程中乘法溢出，这里使用了模数乘法，导致最后的时间复杂度变成 $O(k \log l \cdot \log (m\cdot M))$ ，显然会T。</p><p>如果预处理 $10^i$ 模 $m\cdot M$ 的各项/和，但由于范围过大防止溢出，还是只能通过模数乘法来计算，时间复杂度仍然不变。</p><h3 id="">拆模</h3><p>{% note info no-icon %}</p><p><strong>引理</strong>：</p><p>今有一长度为 $l$ 的十进制数 $n$ ，</p><p>$$
n:=\sum<em>{i=0}^{l-1}d</em>i10^i \ ,\ 0 \le c_i &lt; 10
$$</p><p>那么其 $\lfloor n/m \rfloor \mod M$ 的结果可以表示为：</p><p>$$
\begin{aligned}
\lfloor n/m \rfloor \pmod M &amp;= \lfloor \frac{\sum<em>{i=0}^{l-1}d</em>i10^{i}}{m}  \rfloor \pmod M \
&amp;=\lfloor \frac{\sum<em>{i=2}^{l-1}d</em>i10^{i}}{m} +\frac{(d<em>1d</em>0)<em>{10}}{m}  \rfloor \pmod M \
&amp;=\lfloor \frac{\sum</em>{i=2}^{l-1}d<em>i10^{i-2}}{m} \cdot 10^2 +\frac{(d</em>1d<em>0)</em>{10}}{m}  \rfloor \pmod M \
\end{aligned}
$$</p><p>我们记数字的前i位（高i位）为 $n<em>i:=\sum</em>{j=l-i-1}^{l-1}d_j10^j$ ，那么有：</p><p>$$
\begin{aligned}
q<em>i:&amp;=\lfloor \frac{n</em>i}{m} \rfloor \pmod M = \lfloor \frac{\sum<em>{j=l-i-1}^{l-1}d</em>j10^{j-2}}{m} \rfloor \pmod M \
r<em>i:&amp;=n</em>i \pmod m =\sum<em>{j=l-i-1}^{l-1}d</em>j10^{j-2} \pmod m \
n<em>i&amp;=q</em>i \cdot m+r<em>i \ ,\ 0 \le r</em>i &lt; m
\end{aligned}
$$</p><p>令 $n<em>0=d</em>{l-1}$ 不难发现有递推公式：</p><p>$$
\begin{aligned}
n<em>{i+1}&amp;=n</em>i\cdot 10+d<em>{l-i-1}\
r</em>{i+1}&amp;=n_{i+1} \pmod m \</p><pre class=""><code class="">   &amp;=(10q_im+10r_i+d_{l-i-1}) \pmod m \
   &amp;=10r_i+d_{l-i-1} \pmod m \</code></pre><p>q<em>{i+1}&amp;=\lfloor \frac{n</em>{i+1}}{m} \rfloor \pmod M \</p><pre class=""><code class="">   &amp;=\lfloor \frac{10q_im+10r_i+d_{l-i-1}}{m} \rfloor \pmod M \
   &amp;=10q_i +\lfloor \frac{10r_i+d_{l-i-1}}{m} \rfloor \pmod M \</code></pre><p>\end{aligned}
$$</p><p>这样就可以递推处理商了，而且很好地避免了逆元等情况。</p><p>{% endnote %}</p><p>为了处理乘法范围过大，同时复杂度又不能接受，由上面的引理我们可以想到维护 $q\ ,\ r$ 模M，通过之前的递推公式，我们知道从高位到低位读入时有：</p><p>$$
\begin{aligned}
n<em>{i+1}&amp;=n</em>i\cdot 10^{l<em>i}+c</em>i \sum<em>{j=0}^{l</em>i-1}10^j \
\end{aligned}
$$</p><p>那么其商和余数的递推公式可以写成：</p><p>$$
\begin{aligned}
r<em>{i+1}&amp;=(10^{l</em>i} r<em>i+c</em>i\sum<em>{j=0}^{l</em>i-1}10^j) \pmod m \
q<em>{i+1}&amp;=\left( 10^{l</em>i}q<em>i + \left \lfloor \frac{10^{l</em>i} r<em>i+c</em>i\sum<em>{j=0}^{l</em>i-1}10^j}{m} \right \rfloor  \right) \pmod M
\end{aligned}
$$</p><p>这里使用预处理 $10^i$ 分别模M和m的个项和合，然后把长度 $l$ 用二进制拆成若干个 $2^i$ 的和，这样就可以在 $O(k \log l)$ 的时间复杂度内求出答案。</p><p>{% note warning no-icon %}</p><pre class="language-cpp lang-cpp"><code class="language-cpp lang-cpp">#include &lt;bits/stdc++.h&gt;
using namespace std;

using i128 = __int128;
using ll = long long;

const int LOG=60;
vector&lt;ll&gt; p10m(LOG), p10M(LOG);
vector&lt;ll&gt; s10m(LOG), s10M(LOG);

void init(ll m, ll M)
{
    p10m[0]=10%m,p10M[0]=10%M;
    s10m[0]=1%m,s10M[0]=1%M;

    for(int i=1;i&lt;LOG;i++)
    {
        p10m[i]=(i128)p10m[i-1]*p10m[i-1]%m;
        p10M[i]=(i128)p10M[i-1]*p10M[i-1]%M;
        s10m[i]=((i128)s10m[i-1]*p10m[i-1]+s10m[i-1])%m;
        s10M[i]=((i128)s10M[i-1]*p10M[i-1]+s10M[i-1])%M;
    }
}

pair&lt;i128,i128&gt; calc(i128 l,i128 mod,
                     const vector&lt;ll&gt;&amp; p10,
                     const vector&lt;ll&gt;&amp; s10)
{
    i128 p=1,s=0;
    for(int i=0;l;i++,l&gt;&gt;=1)
        if(l&amp;1)
        {
            p=p*p10[i]%mod;
            s=(s*p10[i]+s10[i])%mod;
        }
    return {p,s};
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int k;ll m,M;
    cin &gt;&gt; k &gt;&gt; m &gt;&gt; M;
    init(m,M);  
    i128 r=0, q=0;
    for(int i=0;i&lt;k;i++)
    {
        ll c,l; 
        cin &gt;&gt; c &gt;&gt; l;
        auto [p,s]=calc(l,m,p10m,s10m);
        auto [P,S]=calc(l,M,p10M,s10M);  
        i128 t=(i128)p*r+(i128)c*s;
        r=t%m;
        q=((i128)P*q+t/m) % M;
    }
    cout &lt;&lt; (ll)q;
}
</code></pre>
<p>但是这样写WA了，原因是对于 $q<em>{i+1}$ 的向下取整的分子应该是除了 $r</em>i$ 剩下都不应该取模，以上程序计算 $10^{l<em>i} r</em>i+c<em>i\sum</em>{j=0}^{l_i-1}10^j$ 时，对于10的次幂以及和都做了取模处理，这显然是错误的。</p><p>如果使用模数为 $mM$ ，同样地，中间过程计算的积肯定会溢出，此时又用懒乘法，导致时间复杂度又变回去了。</p><p>{% endnote %}</p><p>{% note success no-icon %}</p><p>下面给出正确<del>至少可以ac</del>的代码 <del><strong>虽然是pypy版本</strong></del> ，直接完整地算出其分子：</p><pre class="language-python lang-python"><code class="language-python lang-python">import sys

def sol():
    it=iter(map(int, sys.stdin.read().split()))
    try:
        K=next(it); m=next(it); M=next(it)
    except StopIteration: return

    mM = m * M
    P, S = [10%mM], [1%mM]
    for i in range(62):
        S.append((S[i]*P[i]+S[i])%mM)
        P.append((P[i]*P[i])%mM)

    r, q = 0, 0
    for _ in range(K):
        c, l = next(it), next(it)
        p, s, i = 1, 0, 0
        l = l
        while l:
            if l&amp;1:
                s=(s*P[i]+S[i])%mM
                p=(p*P[i])%mM
            l&gt;&gt;=1
            i+=1
        t = r*p+c*s
        q = (q*(p%M)+(t%mM//m))%M # 这里利用之前的结论扩大模数mM
        r = t%m
        
    sys.stdout.write(str(q))

if __name__ == &quot;__main__&quot;:
    sol()
</code></pre>
<p>得益于pypy的JIT，这个代码既可以简洁地保持高精度，又可以在2.5s内跑完。</p><p>{% endnote %}</p><h3 id="">拆彻底</h3></div><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/Tutorial/20250308#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/Tutorial/20250308</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/Tutorial/20250308</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Sun, 08 Mar 2026 16:22:06 GMT</pubDate></item><item><title><![CDATA[Verilog学习记录]]></title><description><![CDATA[<link rel="preload" as="image" href="https://image-host-for-meta.oss-cn-hangzhou.aliyuncs.com/imgHost/balemg4ywpcdw8qeb0.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/default/20250305">https://blog.teslongxiao.cn/posts/default/20250305</a></blockquote><div><h1 id="what-is-verilog">what is verilog</h1><p><code>verilog HDL</code>作为一种硬件描述语言，描述着数字电路的网络关系（电路结构）和逻辑，其编译产物为：</p><ol start="1"><li><code>.vvp</code><code> .vcd</code>
该产物是<strong>仿真</strong>结果生成波形图，编译器会先语法解析<em>Parser</em>，然后展开模块<em>Elaboration</em>，最后仿真生成波形<em>Simulation Kernel</em></li><li><code>.bit</code><code>.sof</code>
该产物是<strong>FPGA配置文件</strong>用来烧录，流程是<code>Verilog-&gt;Synthesis-&gt;RTL Netlist-&gt;Technology Mapping-&gt;Place &amp; Route-&gt;Bitstream</code></li><li><code>.gds</code>
该产物用于芯片设计</li><li><code>.edf</code><code>.v</code>
该产物是网表</li></ol><p>我们编写的是<code>RTL Verilog</code>,一个例子：</p><pre class="language-verilog lang-verilog"><code class="language-verilog lang-verilog">module adder(
    input  [7:0] a,
    input  [7:0] b,
    output [7:0] y
);

assign y = a + b;

endmodule  
</code></pre>
<p>这个就是一个8Bit的加法器，其中:</p><ul><li><code>module adder(input a,b, output y)</code>是这个模块的名字<code>adder</code>以及其输入和输出数据</li><li><code>assign</code>是赋值关键字</li><li><code>endmodule</code>是模块结束关键字，和enddefine差不多</li></ul><h1 id="p1-">P1 语法学习</h1><p>这里OJ主要以<a href="https://hdlbits.01xz.net/">HDLBits</a>为主</p><h2 id="">模块</h2><p>类似cpp的函数，在verilog中，一个运行的子单元以模块为主，例如：</p><pre class="language-verilog lang-verilog"><code class="language-verilog lang-verilog">module example(
    input a,b,
    output y;
);

endmodule;   // 结束模块关键字是必要的
</code></pre>
<p>::: info</p><p>这里的输入和输出端口也可以在模块外部写出</p><pre class="language-verilog lang-verilog"><code class="language-verilog lang-verilog">module example(a,b,c);
    input a,b;
    output c;

endmodule;  
</code></pre>
<p>:::</p>
<h2 id="">赋值</h2><p>在verilog中赋值需要使用关键字<code>assign</code>后跟随方程进行赋值，例如一个简单的输出</p><pre class="language-verilog lang-verilog"><code class="language-verilog lang-verilog">module example(input a1,a2,a3, output y1, y2);
    assign y1=a1, y2=a1, y2=a3; // 这里逗号用法和C/Cpp一样
endmodule;
</code></pre>
<h2 id="">导线</h2><p>导线<code>wire</code>是实际设计中物理存在的符号，在verilog中作为一种存储的数据类型，与其对应的是<code>reg</code>，即寄存器类型</p><p>例如这一个模块（显然是可以化简）：</p><p><img src="https://image-host-for-meta.oss-cn-hangzhou.aliyuncs.com/imgHost/balemg4ywpcdw8qeb0.png" height="513" width="864"/></p></div><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/default/20250305#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/default/20250305</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/default/20250305</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Fri, 06 Mar 2026 08:48:56 GMT</pubDate></item><item><title><![CDATA[记录一次逐飞wifi的debug]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.teslongxiao.cn/posts/Microcontroller/20260304">https://blog.teslongxiao.cn/posts/Microcontroller/20260304</a></blockquote><div><h1 id="">前言</h1><p>使用助飞的wifi模块直接修改<code>main</code>中的ssid和pwd，在串口通信日志中显示无法连接，遂修改其源码进行<code>debug</code></p><h1 id="">代码</h1><p>我添加了具体的调试信息，包括首次使用前扫描<code>WIFI SSID</code>、idle的<code>INT</code>引脚上拉和延长等待时间，具体调试信息例如：</p><pre class=""><code class="">[wifi_spi] wifi_scan result:
SSID:i-WZU RSSI:-66
SSID:WZU-free RSSI:-66
SSID:i-WZU RSSI:-66
SSID:banpaixitong RSSI:-66
SSID:banpaixitong RSSI:-65
SSID:banpa
[wifi_spi] target SSID &#x27;nutin&#x27; NOT found
[wifi_spi] wifi_connect: ssid=&#x27;nutin&#x27; pass=&#x27;121144169&#x27; len=18
[wifi_spi] set_parameter start, cmd=0x10, len=18
[wifi_spi] set_parameter: slave ready for write
[wifi_spi] set_parameter: header+data written
[wifi_spi] wait_idle timeout, level=0
[wifi_spi] set_parameter: idle wait timeout after write
[wifi_spi] wait_idle timeout, level=0
[wifi_spi] wifi_connect result=1 ip=
[wifi_spi] error: wifi_connect failed (code=1)
[MAIN] wifi_spi_init attempt 1 failed
[MAIN] wifi_ssid: nutin, password: 121144169
[MAIN] retrying...

[wifi_spi] init: performing reset...
[wifi_spi] init: reset done, mutex set idle
[wifi_spi] init: get firmware version...
[wifi_spi] firmware version: V2.0.0

[wifi_spi] mac address: 9C:13:9E:C5:5F:18

[wifi_spi] attempt wifi_connect -&gt; ssid: nutin, password: 121144169
[wifi_spi] set_parameter start, cmd=0x12, len=0
[wifi_spi] set_parameter: slave ready for write
[wifi_spi] set_parameter: header+data written
[wifi_spi] set_parameter: slave ready after write
[wifi_spi] set_parameter: reply command=0x80
[wifi_spi] set_parameter: reply OK
[wifi_spi] wifi_scan result:
SSID:banpaixitong RSSI:-66
SSID:DIRECT-50-HP M227f LaserJet RSSI:-66
SSID:TP-LINK-A1 RSSI:-65
SSID:WZU-free RSSI:-65
SSID:WZU-f
[wifi_spi] target SSID &#x27;nutin&#x27; NOT found
[wifi_spi] wifi_connect: ssid=&#x27;nutin&#x27; pass=&#x27;121144169&#x27; len=18
[wifi_spi] set_parameter start, cmd=0x10, len=18
[wifi_spi] set_parameter: slave ready for write
[wifi_spi] set_parameter: header+data written
[wifi_spi] wait_idle timeout, level=0
[wifi_spi] set_parameter: idle wait timeout after write
[wifi_spi] wait_idle timeout, level=0
[wifi_spi] wifi_connect result=1 ip=
[wifi_spi] error: wifi_connect failed (code=1)
[MAIN] wifi_spi_init attempt 2 failed
[MAIN] wifi_ssid: nutin, password: 121144169
[MAIN] retrying...
</code></pre>
<h2 id="zfdevicewifispic"><code>zf_device_wifi_spi.c</code></h2><pre class="language-c lang-c"><code class="language-c lang-c">/*********************************************************************************************************************
* STC32G144K Opensourec Library 即（STC32G144K 开源库）是一个基于官方 SDK 接口的第三方开源库
* Copyright (c) 2025 SEEKFREE 逐飞科技
*
* 本文件是STC32G144K开源库的一部分
*
* STC32G144K 开源库 是免费软件
* 您可以根据自由软件基金会发布的 GPL（GNU General Public License，即 GNU通用公共许可证）的条款
* 即 GPL 的第3版（即 GPL3.0）或（您选择的）任何后来的版本，重新发布和/或修改它
*
* 本开源库的发布是希望它能发挥作用，但并未对其作任何的保证
* 甚至没有隐含的适销性或适合特定用途的保证
* 更多细节请参见 GPL
*
* 您应该在收到本开源库的同时收到一份 GPL 的副本
* 如果没有，请参阅&lt;https://www.gnu.org/licenses/&gt;
*
* 额外注明：
* 本开源库使用 GPL3.0 开源许可证协议 以上许可申明为译文版本
* 许可申明英文版在 libraries/doc 文件夹下的 GPL3_permission_statement.txt 文件中
* 许可证副本在 libraries 文件夹下 即该文件夹下的 LICENSE 文件
* 欢迎各位使用并传播本程序 但修改内容时必须保留逐飞科技的版权声明（即本声明）
*
* 文件名称          
* 公司名称          成都逐飞科技有限公司
* 版本信息          查看 libraries/doc 文件夹内 version 文件 版本说明
* 开发环境          MDK FOR C251
* 适用平台          STC32G144K
* 店铺链接          https://seekfree.taobao.com/
*
* 修改记录
* 日期              作者           备注
* 2025-11-20        大W            first version
********************************************************************************************************************/
/*********************************************************************************************************************
* 接线定义：
*                   ------------------------------------
*                   模块管脚            单片机管脚
*                   RST                 查看 zf_device_wifi_spi.h 中 WIFI_SPI_RST_PIN 宏定义
*                   INT                 查看 zf_device_wifi_spi.h 中 WIFI_SPI_INT_PIN 宏定义
*                   CS                  查看 zf_device_wifi_spi.h 中 WIFI_SPI_CS_PIN 宏定义
*                   MISO                查看 zf_device_wifi_spi.h 中 WIFI_SPI_MISO_PIN 宏定义
*                   SCK                 查看 zf_device_wifi_spi.h 中 WIFI_SPI_SCK_PIN 宏定义
*                   MOSI                查看 zf_device_wifi_spi.h 中 WIFI_SPI_MOSI_PIN 宏定义
*                   5V                  5V 电源
*                   GND                 电源地
*                   其余引脚悬空
*                   ------------------------------------
*********************************************************************************************************************/

#include &quot;stdio.h&quot;
#include &quot;zf_common_clock.h&quot;
#include &quot;zf_common_debug.h&quot;
#include &quot;zf_common_fifo.h&quot;
#include &quot;zf_driver_delay.h&quot;
#include &quot;zf_driver_gpio.h&quot;
#include &quot;zf_driver_spi.h&quot;
#include &quot;zf_device_type.h&quot;

#include &quot;zf_device_wifi_spi.h&quot;

#define WIFI_CONNECT_TIME_OUT       10000       // 单位毫秒
#define SOCKET_CONNECT_TIME_OUT     50000       // 单位毫秒
#define OTHER_TIME_OUT              1000        // 单位毫秒

#if ((WIFI_SPI_RECVIVE_SIZE &lt; 32) || (WIFI_SPI_RECVIVE_SIZE &gt; 4088))
    #error &quot;WIFI_SPI_RECVIVE_SIZE must be &gt;= 32 or &lt;= 4088&quot;
#endif

#if (WIFI_SPI_RECVIVE_SIZE &gt;= WIFI_SPI_RECVIVE_FIFO_SIZE)
    #error &quot;WIFI_SPI_RECVIVE_FIFO_SIZE must be &gt; WIFI_SPI_RECVIVE_SIZE&quot;
#endif


#if (WIFI_SPI_TRANSFER_SIZE != 4088)
    #error &quot;WIFI_SPI_TRANSFER_SIZE must be == 4088&quot;
#endif



char wifi_spi_version[12] = {0};                      // 保存模块固件版本信息
char wifi_spi_mac_addr[20] = {0};                     // 保存模块MAC地址信息
char wifi_spi_ip_addr_port[25] = {0};                 // 保存模块IP地址与端口信息

static fifo_struct  wifi_spi_fifo = {0};
static uint8        wifi_spi_buffer[WIFI_SPI_RECVIVE_FIFO_SIZE] = {0};
static volatile     wifi_spi_state_enum wifi_spi_mutex = {0};
//-------------------------------------------------------------------------------------------------------------------
// 函数简介     等待WIFI SPI就绪
// 参数说明     wait_time       最大等待时间 单位毫秒
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_wait_idle (uint32 wait_time)
{
    uint32 time = 0;
    uint8 level;
    
    wait_time = wait_time*100;
    while(1)
    {
        level = gpio_get_level(WIFI_SPI_INT_PIN);
        if(level)
            break;
        system_delay_us(10);
        time++;
        if(wait_time &lt;= time)
        {
            printf(&quot;[wifi_spi] wait_idle timeout, level=%d\r\n&quot;, level);
            break;
        }
    }
    return (wait_time &lt;= time);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     写入数据到WIFI SPI
// 参数说明     *buffer1        第一组需要发送的数据缓冲区地址
// 参数说明     length1         第一组数据长度
// 参数说明     *buffer2        第二组需要发送的数据缓冲区地址
// 参数说明     length2         第二组数据长度
// 返回参数     void           
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static void wifi_spi_dma_write (const uint8 *buffer1, uint16 length1, const uint8 *buffer2, uint16 length2)
{
    WIFI_SPI_CS(0);
    if(NULL != buffer1)
    {
        spi_dma_write_8bit_array(WIFI_SPI_INDEX, buffer1, length1);
    }
    if(NULL != buffer2)
    {
        spi_dma_write_8bit_array(WIFI_SPI_INDEX, buffer2, length2);
    }
    WIFI_SPI_CS(1);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 发送与接收同时进行（命令收发）
// 参数说明     *packets        发送与接收的地址
// 参数说明     length          需要接收的长度
// 返回参数     void           
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static void wifi_spi_dma_transfer_command (wifi_spi_packets_struct *packets, uint16 length)
{
    WIFI_SPI_CS(0);
    
    spi_dma_transfer_8bit(WIFI_SPI_INDEX, (uint8 *)&amp;(packets-&gt;head), (uint8 *)&amp;(packets-&gt;head), sizeof(wifi_spi_head_struct));
    
    if(length)
    {
        spi_dma_transfer_8bit(WIFI_SPI_INDEX, (const uint8 *)(packets-&gt;buffer), packets-&gt;buffer, length);
    }
    // 大小端不一致，高低字节交换
    packets-&gt;head.length = (uint16)((packets-&gt;head.length&amp;0xFF) &lt;&lt; 8) | ((packets-&gt;head.length &gt;&gt; 8) &amp; 0xFF);
    
    WIFI_SPI_CS(1);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 发送与接收同时进行(数据收发)
// 参数说明     *write_data     发送的数据缓冲区地址
// 参数说明     *read_data      接收到的数据的存储地址
// 参数说明     length          需要接收的长度
// 返回参数     void           
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static void wifi_spi_dma_transfer_data (const uint8 *write_data, wifi_spi_packets_struct *read_data, uint16 length)
{
    WIFI_SPI_CS(0);
    
    read_data-&gt;head.command = WIFI_SPI_DATA;
    // 大小端不一致，高低字节交换
    read_data-&gt;head.length  = (uint16)((length&amp;0xFF) &lt;&lt; 8) | ((length &gt;&gt; 8) &amp; 0xFF);;
    
    spi_dma_transfer_8bit(WIFI_SPI_INDEX, (uint8 *)&amp;(read_data-&gt;head), (uint8 *)&amp;(read_data-&gt;head), sizeof(wifi_spi_head_struct));
    
    if(WIFI_SPI_RECVIVE_SIZE &lt; length)
    {
        spi_dma_transfer_8bit(WIFI_SPI_INDEX, write_data, read_data-&gt;buffer, WIFI_SPI_RECVIVE_SIZE);
        spi_dma_write_8bit_array(WIFI_SPI_INDEX, &amp;write_data[WIFI_SPI_RECVIVE_SIZE], length - WIFI_SPI_RECVIVE_SIZE);
    }
    else
    {
        // 将需要发送的数据拷贝到读取缓冲区，避免出现write_data越界访问
        memcpy(read_data-&gt;buffer, write_data, length);
        spi_dma_transfer_8bit(WIFI_SPI_INDEX, read_data-&gt;buffer, read_data-&gt;buffer, WIFI_SPI_RECVIVE_SIZE);
    }
    
    // 大小端不一致，高低字节交换
    read_data-&gt;head.length = (uint16)((read_data-&gt;head.length&amp;0xFF) &lt;&lt; 8) | ((read_data-&gt;head.length &gt;&gt; 8) &amp; 0xFF);
    
    WIFI_SPI_CS(1);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 参数设置
// 参数说明     command         命令类型
// 参数说明     *buffer         参数地址
// 参数说明     length          参数长度
// 参数说明     wait_time       最大等待时间 单位100微妙
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_set_parameter (wifi_spi_packets_command_enum command, uint8 *buffer, uint16 length, uint32 wait_time)
{
    uint8 return_state;
    wifi_spi_head_struct head;
    return_state = 1;
    printf(&quot;[wifi_spi] set_parameter start, cmd=0x%02X, len=%d\r\n&quot;, command, length);
    do
    {
        head.command = command;
        head.length  = length;
        
        // 等待从机准备就绪
        if(wifi_spi_wait_idle(wait_time))
        {
            printf(&quot;[wifi_spi] set_parameter: idle wait timeout before write\r\n&quot;);
            break;
        }
        printf(&quot;[wifi_spi] set_parameter: slave ready for write\r\n&quot;);

        wifi_spi_dma_write(&amp;head.command, sizeof(wifi_spi_head_struct), buffer, length);
        printf(&quot;[wifi_spi] set_parameter: header+data written\r\n&quot;);
        if(wifi_spi_wait_idle(wait_time))
        {
            printf(&quot;[wifi_spi] set_parameter: idle wait timeout after write\r\n&quot;);
            break;
        }
        printf(&quot;[wifi_spi] set_parameter: slave ready after write\r\n&quot;);
        // 接收应答信号

        head.command = WIFI_SPI_DATA;
        head.length = 0;
        wifi_spi_dma_transfer_command((wifi_spi_packets_struct *)&amp;head, head.length);
        system_delay_us(20);
        printf(&quot;[wifi_spi] set_parameter: reply command=0x%02X\r\n&quot;, head.command);
        if(WIFI_SPI_REPLY_OK == head.command)
        {
            return_state = 0;
            printf(&quot;[wifi_spi] set_parameter: reply OK\r\n&quot;);
        }
        else
        {
            printf(&quot;[wifi_spi] set_parameter: reply not OK\r\n&quot;);
        }
    }while(0);
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 模块信息获取
// 参数说明     command         命令类型
// 参数说明     *buffer         保存接收到的参数地址
// 参数说明     wait_time       最大等待时间 单位100微妙
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     内部使用，用户无需关心
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_get_parameter (wifi_spi_packets_command_enum command, wifi_spi_packets_struct *read_data, uint32 wait_time)
{
    uint8 return_state;

    return_state = 1;
    do
    {
        // 等待从机准备就绪
        if(wifi_spi_wait_idle(wait_time))
        {
            break;
        }
        read_data-&gt;head.command = command;
        wifi_spi_dma_write(&amp;(read_data-&gt;head.command), WIFI_SPI_RECVIVE_SIZE, NULL, 0);

        if(wifi_spi_wait_idle(wait_time))
        {
            break;
        }
        read_data-&gt;head.command = WIFI_SPI_DATA;
        read_data-&gt;head.length = 0;
        wifi_spi_dma_transfer_command(read_data, WIFI_SPI_RECVIVE_SIZE);
        return_state = 0;
    }while(0);
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 固件版本获取
// 参数说明     void            端口号
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     调用函数之后，固件版本信息以字符串形式保存在wifi_spi_version数组中
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_get_version (void)
{
    uint8 return_state;
    wifi_spi_packets_struct temp_packets = {0};

    return_state = wifi_spi_get_parameter(WIFI_SPI_GET_VERSION, &amp;temp_packets, OTHER_TIME_OUT);
    if((0 == return_state) &amp;&amp; (WIFI_SPI_REPLY_VERSION == temp_packets.head.command))
    {
        memcpy(wifi_spi_version, temp_packets.buffer, temp_packets.head.length);
    }
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI MAC地址获取
// 参数说明     void            端口号
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     调用函数之后，MAC地址信息以字符串形式保存在wifi_spi_mac_addr数组中
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_get_mac_addr (void)
{
    uint8 return_state;
    wifi_spi_packets_struct temp_packets;

    return_state = wifi_spi_get_parameter(WIFI_SPI_GET_MAC_ADDR, &amp;temp_packets, OTHER_TIME_OUT);
    if((0 == return_state) &amp;&amp; (WIFI_SPI_REPLY_MAC_ADDR == temp_packets.head.command))
    {
        memcpy(wifi_spi_mac_addr, temp_packets.buffer, temp_packets.head.length);
    }
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI IP地址与端口号获取
// 参数说明     void            端口号
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     调用函数之后，IP地址与端口号信息以字符串形式保存在wifi_spi_ip_addr_port数组中
//              需要在连接Socket之后调用此函数才能正常获取信息
//-------------------------------------------------------------------------------------------------------------------
static uint8 wifi_spi_get_ip_addr_port (void)
{
    uint8 return_state;
    wifi_spi_packets_struct temp_packets;

    return_state = wifi_spi_get_parameter(WIFI_SPI_GET_IP_ADDR, &amp;temp_packets, OTHER_TIME_OUT);
    if((0 == return_state) &amp;&amp; (WIFI_SPI_REPLY_IP_ADDR == temp_packets.head.command))
    {
        memcpy(wifi_spi_ip_addr_port, temp_packets.buffer, temp_packets.head.length);
    }
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 获取系统时间
// 参数说明     time_format     时间格式
// 返回参数     *buffer         保存时间字符串地址 缓冲区大小至少需要30个字节
// 返回参数     buffer_size     缓冲区大小 
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     需要连接有网络的热点之后才能获取到正确的时间，并且在调用这个函数之前需要确保WIFI模块中需要接收的数据已经全部接收完毕
// 备注信息     仅WIFI SPI 外接天线版本支持
// 备注信息     输出的信息为字符串信息，可直接printf到串口助手查看
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_get_time (wifi_spi_time_enum time_format, char *buffer, uint8 buffer_size)
{
    uint8 return_state = 1;
    wifi_spi_packets_struct temp_packets;
    uint8 send_cmd, receive_cmd;
    
    // 接收时间的数组必须大于30个字节
    // 并且模块固件必须是V2版本
    if((30 &lt;= buffer_size) &amp;&amp; (!strncmp(wifi_spi_version, &quot;V2&quot;, 2)))
    {
        send_cmd = WIFI_SPI_GET_TIME1 + time_format - WIFI_SPI_UTC_0;
        receive_cmd = WIFI_SPI_REPLY_TIME1 + time_format - WIFI_SPI_UTC_0;

        return_state = wifi_spi_get_parameter(send_cmd, &amp;temp_packets, OTHER_TIME_OUT);
        if((0 == return_state) &amp;&amp; (receive_cmd == temp_packets.head.command))
        {
            return_state = strncmp((const char *)temp_packets.buffer, &quot;OK&quot;, 2);
            if(0 == return_state)
            {
                memcpy(buffer, &amp;temp_packets.buffer[3], temp_packets.head.length - 3);
                buffer[temp_packets.head.length - 3] = 0;
            }
        }
    }
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 扫描热点
// 参数说明     *buffer         保存扫描到的热点信息 包含名称与信号强度，每个信号强度后面会跟一个换行符
// 参数说明     buffer_size     缓冲区的长度
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     仅WIFI SPI 外接天线版本支持
// 备注信息     输出的信息为字符串信息，可直接printf到串口助手查看
// 备注信息     每一行包含一个wifi名称与密码 
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_wifi_scan (char *buffer, uint16 buffer_size)
{
    uint8 return_state = 0;

    
    // 模块必须是V2版本的固件才支持此功能
    if(!strncmp(wifi_spi_version, &quot;V2&quot;, 2))
    {
        return_state = wifi_spi_set_parameter(WIFI_SPI_SET_WIFI_SCAN, NULL, 0, WIFI_CONNECT_TIME_OUT);
        
        if(0 == return_state)
        {
        #if (0 == WIFI_SPI_READ_TRANSFER)
            wifi_spi_send_buffer(NULL, 0);
        #endif
            while(0 == wifi_spi_read_buffer((uint8 *)buffer, buffer_size))
            {
                system_delay_ms(10);
            }
        }
    }
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 设置连接的WiFi信息并尝试连接WiFi
// 参数说明     *wifi_ssid      WIFI名称
// 参数说明     *pass_word      WIFI密码
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     wifi_spi_wifi_connect(&quot;SEEKFREE&quot;, &quot;SEEKFREE123&quot;);
// 备注信息     wifi_spi_wifi_connect(&quot;SEEKFREE&quot;, NULL); // 连接没有密码的WIFI热点
//-------------------------------------------------------------------------------------------------------------------
// helper: simple substring search (returns 1 if found)
static uint8 wifi_spi_str_contains(const char *haystack, const char *needle)
{
    const char *p, *q;
    if(NULL == haystack || NULL == needle)
        return 0;
    while(*haystack)
    {
        p = haystack;
        q = needle;
        while(*p &amp;&amp; *q &amp;&amp; (*p == *q))
        {
            p++;
            q++;
        }
        if(!*q) // reached end of needle
            return 1;
        haystack++;
    }
    return 0;
}

uint8 wifi_spi_wifi_connect (char *wifi_ssid, char *pass_word)
{
    uint8 return_state;
    uint8 temp_buffer[64];
    uint16 length;
    char scan_buf[128];

    // 在尝试连接之前先扫描附近的热点
    scan_buf[0] = &#x27;\0&#x27;;
    return_state = wifi_spi_wifi_scan(scan_buf, sizeof(scan_buf)-1);
    if(0 == return_state)
    {
        printf(&quot;[wifi_spi] wifi_scan result:\r\n%s\r\n&quot;, scan_buf);
        if(wifi_spi_str_contains(scan_buf, wifi_ssid))
        {
            printf(&quot;[wifi_spi] target SSID &#x27;%s&#x27; found\r\n&quot;, wifi_ssid);
        }
        else
        {
            printf(&quot;[wifi_spi] target SSID &#x27;%s&#x27; NOT found\r\n&quot;, wifi_ssid);
        }
    }
    else
    {
        printf(&quot;[wifi_spi] wifi_scan error %d\r\n&quot;, return_state);
    }

    if(NULL != pass_word)
    {
        // WIFI热点有密码发送热点名称与密码
        length = sprintf((char *)temp_buffer, &quot;%s\r\n%s\r\n&quot;, wifi_ssid, pass_word);
    }
    else
    {
        // WIFI热点没有密码只需要发送热点名称
        length = sprintf((char *)temp_buffer, &quot;%s\r\n&quot;, wifi_ssid);
    }
    
    /* debug info */
    printf(&quot;[wifi_spi] wifi_connect: ssid=&#x27;%s&#x27; pass=&#x27;%s&#x27; len=%d\r\n&quot;, wifi_ssid, (pass_word?pass_word:&quot;(none)&quot;), length);
    return_state = wifi_spi_set_parameter(WIFI_SPI_SET_WIFI_INFORMATION, temp_buffer, length, WIFI_CONNECT_TIME_OUT);
    
    // 本机IP地址与端口号信息以字符串形式保存在wifi_spi_ip_addr_port数组中
    wifi_spi_get_ip_addr_port();
    printf(&quot;[wifi_spi] wifi_connect result=%d ip=%s\r\n&quot;, return_state, wifi_spi_ip_addr_port);
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 设置连接的Socket信息并尝试连接Socket
// 参数说明     *transport_type 传输类型
// 参数说明     *ip_addr        IP地址
// 参数说明     *port           目标端口号
// 参数说明     *local_port     本机端口号
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     wifi_spi_socket_connect(&quot;TCP&quot;, &quot;192.168.2.5&quot;, &quot;8080&quot;, &quot;6060&quot;);
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_socket_connect (char *transport_type, char *ip_addr, char *port, char *local_port)
{
    uint8 return_state;
    uint8 temp_buffer[41];
    uint16 length;
    
    printf(&quot;[wifi_spi] socket_connect: transport=%s target=%s:%s local=%s\r\n&quot;, transport_type, ip_addr, port, local_port);
    length = sprintf((char *)temp_buffer, &quot;%s\r\n%s\r\n%s\r\n%s\r\n&quot;, transport_type, ip_addr, port, local_port);
        
    return_state = wifi_spi_set_parameter(WIFI_SPI_SET_SOCKET_INFORMATION, temp_buffer, length, SOCKET_CONNECT_TIME_OUT);
    
    // 本机IP地址与端口号信息以字符串形式保存在wifi_spi_ip_addr_port数组中
    wifi_spi_get_ip_addr_port();
    printf(&quot;[wifi_spi] socket_connect result=%d ip=%s\r\n&quot;, return_state, wifi_spi_ip_addr_port);
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 断开Socket连接
// 参数说明     void            
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     wifi_spi_socket_disconnect();
// 备注信息
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_socket_disconnect (void)
{
    wifi_spi_packets_struct temp_packets;

    return wifi_spi_get_parameter(WIFI_SPI_CLOSE_SOCKET, &amp;temp_packets, OTHER_TIME_OUT);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 软复位
// 参数说明     void            
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_reset (void)
{
    uint8 return_state;
    wifi_spi_head_struct head;
    return_state = 1;
    do
    {
        head.command = WIFI_SPI_RESET;
        head.length  = 0xA5A5;
        return_state = wifi_spi_wait_idle(OTHER_TIME_OUT);
        if(return_state)
        {
            break;
        }
        wifi_spi_dma_write(&amp;head.command, sizeof(wifi_spi_head_struct), NULL, 0);
    }while(0);
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI UDP模式时立即发送函数
// 参数说明     void
// 返回参数     uint8           状态 0-成功 1-错误
// 使用示例     
// 备注信息     在UDP模式下模块收到数据后会等待2毫秒，2毫秒后未收到数据则将数据通过socket发送到网络，如果希望立即发送则在数据传输完毕后调用此函数
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_udp_send_now (void)
{
    uint8 return_state = 1;
    wifi_spi_packets_struct temp_packets;
    
    if(WIFI_SPI_IDLE == wifi_spi_mutex)
    {
        // 将通讯状态设置为忙
        wifi_spi_mutex = WIFI_SPI_BUSY;
        do
        {
            if(wifi_spi_wait_idle(OTHER_TIME_OUT))
            {
                break;
            }

            // 立即开始socket发送
            temp_packets.head.command = WIFI_SPI_UDP_SEND;
            temp_packets.head.length = 0;
            wifi_spi_dma_transfer_command(&amp;temp_packets, WIFI_SPI_RECVIVE_SIZE);
            
            // 检查收到的包中是否有数据
            if((WIFI_SPI_REPLY_DATA_START == temp_packets.head.command) || (WIFI_SPI_REPLY_DATA_END == temp_packets.head.command))
            {
                // 保存接收到的数据
                if(temp_packets.head.length)
                {
                    fifo_write_buffer(&amp;wifi_spi_fifo, temp_packets.buffer, temp_packets.head.length);
                }
            }
            
            // 等待应答信号
            if(wifi_spi_wait_idle(OTHER_TIME_OUT))
            {
                break;
            }
            
            // 接收应答信号
            temp_packets.head.command = WIFI_SPI_DATA;
            temp_packets.head.length = 0;
            wifi_spi_dma_transfer_command(&amp;temp_packets, temp_packets.head.length);
            
            if(WIFI_SPI_REPLY_OK == temp_packets.head.command)
            {
                return_state = 0;
            }
            
        }while(0);
        
        // 将通讯状态设置为空闲
        wifi_spi_mutex = WIFI_SPI_IDLE;
    } 
    
    return return_state;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 数据块发送函数并同步接收数据
// 参数说明     *buff           需要发送的数据地址
// 参数说明     length          发送长度
// 返回参数     uint32          剩余未发送的长度
// 使用示例     wifi_spi_send_buffer(buffer, 100);
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
uint32 wifi_spi_send_buffer (const uint8 *buffer, uint32 length)
{
    uint16 send_length;
    wifi_spi_packets_struct temp_packets;
    
    // 检查WIFI SPI状态，如果在其他中断或者线程中已经发起了通讯，则本次不能发送数据
    if(WIFI_SPI_IDLE == wifi_spi_mutex)
    {
        // 将通讯状态设置为忙
        wifi_spi_mutex = WIFI_SPI_BUSY;
        
        while(length)
        {
            send_length = length &gt; WIFI_SPI_TRANSFER_SIZE ? WIFI_SPI_TRANSFER_SIZE : length;
            
            if(wifi_spi_wait_idle(OTHER_TIME_OUT))
            {
                break;
            }
            
            wifi_spi_dma_transfer_data(buffer, &amp;temp_packets, send_length);
            
            // 检查收到的包中是否有数据
            if((WIFI_SPI_REPLY_DATA_START == temp_packets.head.command) || (WIFI_SPI_REPLY_DATA_END == temp_packets.head.command))
            {
                // 保存接收到的数据
                if(temp_packets.head.length)
                {
                    fifo_write_buffer(&amp;wifi_spi_fifo, temp_packets.buffer, temp_packets.head.length);
                }
            }
            
            length -= send_length;
            buffer += send_length;
        }
        
        // 检查最后一次的接收是否将所有的数据都接收完毕
        while(WIFI_SPI_REPLY_DATA_START == temp_packets.head.command)
        {
            if(wifi_spi_wait_idle(OTHER_TIME_OUT))
            {
                break;
            }
            
            // 继续读取模块剩余数据
            temp_packets.head.command = WIFI_SPI_DATA;
            temp_packets.head.length  = 0;
            wifi_spi_dma_transfer_command(&amp;temp_packets, WIFI_SPI_RECVIVE_SIZE);
            // 检查收到的包中是否有数据
            if((WIFI_SPI_REPLY_DATA_START == temp_packets.head.command) || (WIFI_SPI_REPLY_DATA_END == temp_packets.head.command))
            {
                // 保存接收到的数据
                if(temp_packets.head.length)
                {
                    fifo_write_buffer(&amp;wifi_spi_fifo, temp_packets.buffer, temp_packets.head.length);
                }
            }
        }
        wifi_spi_mutex = WIFI_SPI_IDLE;
    }
    return length;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 字符串发送函数并同步接收数据
// 参数说明     *string           需要发送的字符串
// 返回参数     void          
// 使用示例     wifi_spi_send_string(&quot;123&quot;);
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
void wifi_spi_send_string(const char *string)
{
    wifi_spi_send_buffer((uint8*)string, strlen(string));
}
    
//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WIFI SPI 读取缓冲区
// 参数说明     *buff           接收缓冲区
// 参数说明     length          读取数据长度
// 返回参数     uint32          实际读取数据长度
// 使用示例     wifi_spi_read_buffer(buffer, 100);
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
uint32 wifi_spi_read_buffer (uint8 *buffer, uint32 length)
{
    uint32 fifo_read_length;
    uint32 write_length = 0;
    wifi_spi_packets_struct temp_packets;

    zf_assert(NULL != buffer);

    // 首先判断FIFO中是否有数据，如果有则先从FIFO读取
    if(fifo_used(&amp;wifi_spi_fifo))
    {
        fifo_read_length = fifo_used(&amp;wifi_spi_fifo);
        fifo_read_length = length &lt; fifo_read_length ? length : fifo_read_length;
        fifo_read_buffer(&amp;wifi_spi_fifo, buffer, &amp;fifo_read_length, FIFO_READ_AND_CLEAN);
        
        buffer += fifo_read_length;
        length -= fifo_read_length;
        write_length += fifo_read_length;
    }
    
#if(1 == WIFI_SPI_READ_TRANSFER)
    if(WIFI_SPI_IDLE == wifi_spi_mutex)
    {
        wifi_spi_mutex = WIFI_SPI_BUSY;                         // 将通讯状态设置为忙
        
        do
        {
            if( (WIFI_SPI_RECVIVE_SIZE &gt; wifi_spi_fifo.siz) || // 如果缓冲区空间不够则不再读取
                (wifi_spi_wait_idle(OTHER_TIME_OUT))            // 超时退出
              )
            {
                break;
            }
            
            temp_packets.head.command = WIFI_SPI_DATA;
            temp_packets.head.length  = 0;
            wifi_spi_dma_transfer_command(&amp;temp_packets, WIFI_SPI_RECVIVE_SIZE);
            // 检查收到的包中是否有数据
            if( ((WIFI_SPI_REPLY_DATA_START == temp_packets.head.command) || (WIFI_SPI_REPLY_DATA_END == temp_packets.head.command)) &amp;&amp;
                (temp_packets.head.length)
              )
            {
                if(length)
                {
                    fifo_read_length = length &gt;= temp_packets.head.length ? temp_packets.head.length : length;
                    memcpy(buffer, temp_packets.buffer, (uint32)fifo_read_length);
                    buffer += fifo_read_length;
                    length -= fifo_read_length;
                    write_length += fifo_read_length;
                    if(fifo_read_length &lt; temp_packets.head.length) // 外部缓冲不足多余部分写入FIFO
                    {
                        fifo_write_buffer(&amp;wifi_spi_fifo, temp_packets.buffer + fifo_read_length, temp_packets.head.length - fifo_read_length);
                    }
                }
                else                                                // 全部写入FIFO
                {
                    fifo_write_buffer(&amp;wifi_spi_fifo, temp_packets.buffer, temp_packets.head.length);
                }
            }
        }while(WIFI_SPI_REPLY_DATA_START == temp_packets.head.command);
        wifi_spi_mutex = WIFI_SPI_IDLE;
    }
#endif
    
    return write_length;
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     WiFi 模块初始化
// 参数说明     *wifi_ssid      目标连接的 WiFi 的名称 字符串形式
// 参数说明     *pass_word      目标连接的 WiFi 的密码 字符串形式
// 返回参数     uint8           模块初始化状态 0-成功 1-错误
// 使用示例     wifi_spi_init(&quot;SEEKFREE&quot;, &quot;SEEKFREE123&quot;);
// 备注信息     wifi_spi_init(&quot;SEEKFREE&quot;, NULL); // 连接没有密码的WIFI热点
//-------------------------------------------------------------------------------------------------------------------
uint8 wifi_spi_init (char *wifi_ssid, char *pass_word)
{
    uint8 return_state = 0;


    fifo_init(&amp;wifi_spi_fifo, FIFO_DATA_8BIT, wifi_spi_buffer, WIFI_SPI_RECVIVE_FIFO_SIZE);
    spi_dma_init(WIFI_SPI_INDEX, SPI_MODE3, WIFI_SPI_SPEED, WIFI_SPI_SCK_PIN, WIFI_SPI_MOSI_PIN, WIFI_SPI_MISO_PIN, SPI_CS_NULL);//硬件SPI初始化

    gpio_init(WIFI_SPI_CS_PIN,  GPO, 1, GPO_PUSH_PULL);
    gpio_init(WIFI_SPI_RST_PIN, GPO, 1, GPO_PUSH_PULL);
    gpio_init(WIFI_SPI_INT_PIN, GPI, 0, GPI_PULL_DOWN);
    
    // 复位
    printf(&quot;[wifi_spi] init: performing reset...\r\n&quot;);
    gpio_set_level(WIFI_SPI_RST_PIN, 0);
    system_delay_ms(10);
    gpio_set_level(WIFI_SPI_RST_PIN, 1);
    
    // 等待模块初始化
    system_delay_ms(100);
    wifi_spi_mutex = WIFI_SPI_IDLE;
    printf(&quot;[wifi_spi] init: reset done, mutex set idle\r\n&quot;);

    do
    {
        // 固件版本信息以字符串形式保存在wifi_spi_version数组中
        printf(&quot;[wifi_spi] init: get firmware version...\r\n&quot;);
        return_state = wifi_spi_get_version();
        if(return_state)
        {
            printf(&quot;[wifi_spi] error: get_version failed (code=%d)\r\n&quot;, return_state);
            break;
        }
        else
        {
            printf(&quot;[wifi_spi] firmware version: %s\r\n&quot;, wifi_spi_version);
        }
      
        // MAC地址信息以字符串形式保存在wifi_spi_mac_addr数组中
        wifi_spi_get_mac_addr();
        printf(&quot;[wifi_spi] mac address: %s\r\n&quot;, wifi_spi_mac_addr);

        if(NULL == wifi_ssid)
        {
            // 初始化的时候不需要连接WIFI
            printf(&quot;[wifi_spi] no SSID provided, skip wifi connect\r\n&quot;);
            break;
        }

        printf(&quot;[wifi_spi] attempt wifi_connect -&gt; ssid: %s, password: %s\r\n&quot;, wifi_ssid, (pass_word?pass_word:&quot;(none)&quot;));
        return_state = wifi_spi_wifi_connect(wifi_ssid, pass_word);
        if(return_state)
        {
            printf(&quot;[wifi_spi] error: wifi_connect failed (code=%d)\r\n&quot;, return_state);
            break;
        }
        else
        {
            // 获取并打印本机 IP 信息
            wifi_spi_get_ip_addr_port();
            printf(&quot;[wifi_spi] wifi connected, ip: %s\r\n&quot;, wifi_spi_ip_addr_port);
        }
        
    #if(1 == WIFI_SPI_AUTO_CONNECT)
        printf(&quot;[wifi_spi] AUTO_CONNECT enabled, attempting TCP socket connect to %s:%s\r\n&quot;, WIFI_SPI_TARGET_IP, WIFI_SPI_TARGET_PORT);
        return_state = wifi_spi_socket_connect(&quot;TCP&quot;, WIFI_SPI_TARGET_IP, WIFI_SPI_TARGET_PORT, WIFI_SPI_LOCAL_PORT);
        if(return_state)  
        {
            printf(&quot;[wifi_spi] error: auto TCP socket_connect failed (code=%d)\r\n&quot;, return_state);
            break;
        }
        else
        {
            printf(&quot;[wifi_spi] auto TCP socket connected, local/remote: %s\r\n&quot;, wifi_spi_ip_addr_port);
        }
    #endif
        
    #if(2 == WIFI_SPI_AUTO_CONNECT)
        return_state = wifi_spi_socket_connect(&quot;UDP&quot;, WIFI_SPI_TARGET_IP, WIFI_SPI_TARGET_PORT, WIFI_SPI_LOCAL_PORT);
        if(return_state)
        {
            break;
        }
    #endif
    }while(0);

    return return_state;
}

</code></pre>
<h2 id="mainc"><code>main.c</code></h2><pre class="language-c lang-c"><code class="language-c lang-c">    while(wifi_spi_init(WIFI_SSID_TEST, WIFI_PASSWORD_TEST))
    {
+       init_attempt++;
+       printf(&quot;[MAIN] wifi_spi_init attempt %d failed\r\n&quot;, init_attempt);  
+       if(wifi_pw)
+           printf(&quot;[MAIN] wifi_ssid: %s, password: %s\r\n&quot;, WIFI_SSID_TEST, wifi_pw);
+       else
+           printf(&quot;[MAIN] wifi_ssid: %s, password: (none)\r\n&quot;, WIFI_SSID_TEST);

+       printf(&quot;[MAIN] retrying...\r\n\r\n&quot;);
        system_delay_ms(100);                                                   // 初始化失败 等待 100ms
    }
</code></pre>
<h1 id="">结论</h1><p>该<code>wifi scan</code>输出缓存只有128字节，输出没有我的热点不代表没有找到，看样子还是没有把<code>GPIO INT</code>拉高。</p><p>后续发现队友可以成功连接wifi，说明该程序的<code>wifi_spi_set_parameter</code>函数中两个中断没有问题，此时才视角回到wifi源头（热点），看手机上开启了<strong>第六代技术</strong>(<code>802.11ax</code>)导致这种单片机无法使用新技术，一直在尝试连接然后超时了，之后关闭这个选项单片机就成功连接上<code>TCP</code>服务器，但是看<code>main</code>里面的摄像头开启函数，无输出<code>log</code>，查看具体的摄像头<code>init</code>发现应该是找搜寻设备，应该还是超时了，进程一直卡在搜索中，具体没写<code>debug</code>的<code>log</code>，之后还需添加log进行调试。</p></div><p style="text-align:right"><a href="https://blog.teslongxiao.cn/posts/Microcontroller/20260304#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.teslongxiao.cn/posts/Microcontroller/20260304</link><guid isPermaLink="true">https://blog.teslongxiao.cn/posts/Microcontroller/20260304</guid><dc:creator><![CDATA[meta]]></dc:creator><pubDate>Thu, 05 Mar 2026 02:34:48 GMT</pubDate></item></channel></rss>