《算法竞赛进阶指南》笔记
《算法竞赛进阶指南》笔记
\(\texttt{0x00}\) 前言
本文记录了蒟蒻对书中所有例题与习题的见解/分析。题目均收录于Acwing-算法竞赛进阶指南 或 LG 题单(荐)。
于 \(\texttt{2023/6/9}\) 开始动笔,之后将会持续更新。
upd:因为笔者懒得更新,所以暂时取消目录不会有目录了。
upd:暂时断更,CSP 之后考虑继续更新。
upd:大概更新要到 \(\texttt{2024}\) 年去了。敬请期待 qwq。
upd:new。
upd:此文永久停止更新。(2024/12/13)
upd:重新开始更新,从数学一章开始。(2025/1/8)
upd:随缘更新。以后将不会发布更新公告。
\(\texttt{0x01}\) 位运算
- 先放一张个人认为很重要的表
操作 | 运算 |
---|---|
取出整数\(n\) 在二进制下的第 \(k\) 位 | \((n>>k) \And 1\) |
取出整数\(n\) 在二进制下的后 \(k\) 位 | \(n \And ((1<<k)-1)\) |
把整数\(n\) 在二进制下的第 \(k\) 位取反 | \(n \ \text{xor} \ (1<<k)\) |
对整数\(n\) 在二进制下的第 \(k\) 位赋值 \(1\) | \(n \mid (1<<k)\) |
对整数\(n\) 在二进制下的第 \(k\) 位赋值 \(0\) | \(n \And (\sim(1<<k))\) |
【例题 \(\texttt{1}\)】 \(a^b\)
若 \(b\) 在二进制下有 \(n\) 位,第 \(i\) 位为 \(c_i\),则有:
\( a^b=a^{c_{n-1} \times 2^{n-1}} \times a^{c_{n-2} \times 2^{n-2}} \times ... \times a^{c_0} \)
又因:
\( a^{2^n}=a^{2^{n-1}} \times a^{2^{n-1}} \)
所以我们便可不断地取出 \(b\) 的每一位 \(c_i\),若 \(c_i=1\),则让 \(ans\) 乘上 \(a^{2^i}\);同时让 \(a\) 不断地自乘得出乘积项。
code。
【例题 \(\texttt{2}\)】 64位整数乘法
将 \(b\) 拆成二进制每一位数相加的形式(记共有 \(n\) 位,第 \(i\) 位为 \(c_i\)),可得:
\( a \times b = a \times c_{n-1} \times 2^{n-1} + a \times c_{n-2} \times 2^{n-2} + ... + a \times c_{0} \)
又因:
\( a \times 2^n = a \times 2^{n-1} + a \times 2^{n-1} \)
所以又可以不断地取出 \(b\) 的每一位 \(c_i\),若 \(c_i=1\),则让 \(ans\) 加上 \(a^{2^i}\);同时让 \(a\) 不断乘 \(2\) 得出乘积项。
code。
【例题 \(\texttt{3}\)】 最短 Hamilton 路径
令 \(\text{F}(i,j)\) 表示目前走过点的状态的二进制数为 \(i\),且当前处于点 \(j\) 时的最短路径长度。易知在起点时有 \(\text{F}(1,0)=0\),目标是 \(\text{F}((1<<n)-1,n-1)\)。
递推公式:\(\text{F}(i,j)=\min\{\text{F}(i \ \text{xor} \ (1<<j),k) + w(k,j)\}\),其中 \(w(i,j)\) 指点 \(i\) 到点 \(j\) 的距离,需要满足 \(0 \le k < n\) 且 \((i>>j) \And 1\)。因为上一时刻的点 \(k\) 可能是 \(i \ \text{xor} \ (1<<j)\) 任意一个为 \(1\) 的点,所以 \(O(n^3)\) 的枚举此点取最小值即可。
code。
【例题 \(\texttt{4}\)】 起床困难综合征
位运算的特点:在二进制下不进位。
因此,我们可以枚举每一位,分别计算出这一位填 \(0/1\) 的经过运算后的值,若填 \(1\) 不会超过最大值 \(m\) 且比填 \(0\) 的结果更大,那么填 \(1\);否则填 \(0\)。
当每一位都填好后,\(ans\) 也就呼之欲出了。
code。
\(\texttt{0x02}\) 递推与递归
- 放一张有用的表
枚举形式 | 状态空间规模 | 一般遍历方式 |
---|---|---|
多项式 | \(n^k\)(\(k\) 为常数) | for 循环、递推 |
指数 | \(k^n\)(\(k\) 为常数) | 递归、位运算 |
排列 | \(n!\) | 递归、C++ next_permutation |
组合 | \(\dbinom{n}{k}\) | 递归\(+\) 剪枝 |
【例题 \(\texttt{5/6/7}\)】递归实现指数 \(/\) 排列 \(/\) 组合型枚举
这三道题几乎是一样的,所以放到一起讲了。
- 指数型枚举:用一个
vector
数组存储被选的数,分别对于"不选 \(/\) 选 \(x\)" 分支分别进行处理即可。code。 - 排列型枚举:与指数型枚举类似,只是需要一个
vis
数组来记录每个数是否被选过。笔者使用了C++ next_permutation
实现,更为简单。code。 - 组合型枚举:一般的,仅需在指数型枚举的
dfs
开头加上if(a.size()>m||a.size()+(n-x+1)<m) return;
即可,这样便可实现只选 \(m\) 个数;特殊的,若题目要求按字典序输出,则需要用到不降原则的知识,实现详见代码。code。
【例题 \(\texttt{8}\)】费解的开关
首先,用位运算枚举第一行的 \(2^5=32\) 种填法。
接着从第一行开始递推,若 \(i\) 行 \(j\) 列的数为 \(1\),则点击 \(i+1\) 行 \(j\) 列的数。
若到达第 \(5\) 行仍不为全 \(0\) 或超过了 \(6\) 步,则视为不合法的操作。
对所有合法操作的步数取最小值即可。
code。
【例题 \(\texttt{9}\)】\(\text{Strange Towers of Hanoi}\)
对于 \(n\) 盘 \(3\) 塔的经典 \(\text{Hanoi}\) 问题,令 \(d(i)\) 表示求解 \(i\) 盘 \(3\) 塔所需的最小步数,则有:
\( d(i) = 2 \times d(i-1) + 1 \)
(即先将 \(i-1\) 个盘子移动到 \(\text{B}\) 柱,再将第 \(i\) 个盘子移动到 \(\text{C}\) 柱,最后将 \(i-1\) 个盘子移动到 \(\text{C}\) 柱。)
对于 \(n\) 盘 \(4\) 塔的 \(\text{Hanoi}\) 问题,则同样令 \(f(i)\) 表示求解 \(i\) 盘 \(4\) 塔所需的最小步数,则又有:
\( f(i)=\mathop{\min}\limits_{1 \le j < n} \{ 2 \times f(j) + d(i-j) \} \)
(即先让 \(j\) 个盘子在 \(4\) 塔模式下移动到 \(\text{B}\) 柱,再让 \(i-j\) 个盘子在 \(3\) 塔模式下移动到 \(\text{D}\) 柱,最后让 \(j\) 个盘子在 \(4\) 塔模式下移动到 \(\text{D}\) 柱,并在所有这样的 \(j\) 中取最小值。)
于是对于每一个 \(n\),先 \(O(n)\) 地求出每个 \(d(i)\),再 \(O(n^2)\) 地求出每个 \(f(i)\),最后输出 \(f(n)\) 即可。
\(\texttt{Tips: 多测不清空,爆零两行泪!}\)
code。
【例题 \(\texttt{10}\)】\(\text{Sumdiv}\)
(注:本题解与书上方法略有不同)
首先对 \(a\) 分解质因数,并记录次幂,注意特判最后 \(a \neq 1\) 的情况。
设
\( a=p_1^{c_1} \times p_2^{c_2} \times ... \times p_n^{c_n} \)
则
\( a^b=p_1^{c_1 \times b} \times p_2^{c_2 \times b} \times ... \times p_n^{c_n \times b} \)
因此易知 \(a^b\) 的约数和为
\( (1+p_1+...+p_1^{c_1 \times b}) \times (1+p_2+...+p_2^{c_2 \times b}) \times ... \times (1+p_n+...+p_n^{c_n \times b}) \)
其中每一项均是等比数列,我们知道等比数列的求和公式为
\( sum=\dfrac{p^n-1}{p-1} \)
分子可以使用快速幂求出,由于本题需要进行取模,所以我们考虑将 \(\div \ (p-1)\) 转化为 \(\times \ (p-1)\) 的逆元。
根据费马小定理,当 \(a \nmid p\) 时,有
\( a^{p-1} \equiv 1 \pmod p \)
即
\( a \times a^{p-2} \equiv 1 \pmod p \)
根据逆元的定义,若
\( ax \equiv 1 \pmod p \)
则称 \(x\) 为 \(a\) 的逆元。
于是在本题中,\(a^{p-2}\) 即为 \(a\) 的逆元,\(a^{p-2}\) 同样也可以用快速幂求出。本题被完美解决~
code。
【例题 \(\texttt{11}\)】\(\text{Fractal Streets}\)
比较难想的一道题。
首先令 \(calc(n,m)\) 表示编号为 \(m\) 的房屋在第 \(n\) 级城市中的位置。
不难发现 \(n\) 级城市总是由 \(4\) 座 \(n-1\) 级城市组成(因为 \(\dfrac{(2^n)^2}{(2^{n-1})^2}=4\))。
于是可以递归地求解 \(calc(n-1,m \bmod 2^{2n-2})\),记此位置为 \((x,y)\)。
再根据 \(\dfrac{m}{2^{2n-2}}\) 的大小即可确定此位置在哪一座 \(n-1\) 级城市之中(\(0,1,2,3\) 分别对应左上、右上、左下、右下)。
- 若 \((x,y)\) 处于左上的 \(n-1\) 级城市中,则需要将坐标顺时针旋转 \(90^{\circ}\) 再水平翻转,得到坐标 \((y,x)\)。
- 若 \((x,y)\) 处于右上的 \(n-1\) 级城市中,则直接将纵坐标 \(+ \ 2^{n-1}\) 即可。
- 若 \((x,y)\) 处于右下的 \(n-1\) 级城市中,则直接将横、纵坐标均 \(+ \ 2^{n-1}\) 即可。
- 若 \((x,y)\) 处于左上的 \(n-1\) 级城市中,则需要将坐标顺时针旋转 \(90^{\circ}\) 再水平翻转,最后将横坐标 \(+ \ 2^{n-1}\),得到坐标 \((2^{n}-y-1,2^{n-1}-x-1)\)。
根据以上思路编写 \(calc\) 函数即可将本题解决。
code。
\(\texttt{0x03}\) 前缀和与差分
【例题 \(\texttt{12}\)】激光炸弹
前言:\(\text{std}\) 能过 \(\texttt{Luogu}\),但过不了 \(\texttt{Acwing}\),所以本题解以 \(\texttt{Acwing}\) 数据为准。
首先需要用 \(A\) 数组存下每个目标的价值。
接着,很容易想到的思路便是对于 \(A\) 数组求出它的二维前缀和数组 \(S\),接着 \(O(n^2)\) 地枚举 \(R \times R\) 正方形的右下角顶点,算出它的价值并取 \(\max\) 即可。为了优化空间,可以直接省略 \(A\) 数组。这样的思路足以通过 \(\texttt{Luogu}\) 数据。
但是 \(\texttt{Acwing}\) 的数据不保证 \(R\) 一定 \(\le \max\{x_i\},\max\{y_i\}\),因此我们只需要特判一下:
- 当 \(R>\max\{x_i\}\) 时,取 \(\max\{x_i\}\) 行的最大值。
- 当 \(R>\max\{y_i\}\) 时,取 \(\max\{y_i\}\) 列的最大值。
- 当 \(R \le \max\{x_i\},\max\{y_i\}\) 时,取 \(\max\{x_i\}\) 行、\(\max\{y_i\}\) 列的最大值。
- 否则,按照原来的思路实现。
这样就可以过掉加强版的数据了~
code。
【例题 \(\texttt{13}\)】 \(\text{IncDec Sequence}\)
为使程序效率提升,我们先要求出 \(a\) 的差分数组 \(b\),以将区间操作转化为单点操作。特殊的,需要令 \(b_{n+1}=0\)。
那么题中对于 \(a\) 数组的操作,实际上就相当于从 \(b_{1 \sim n+1}\) 中选择任意两数,一个 \(+1\),一个 \(-1\),目标是让 \(b_{2 \sim n}\) 均 \(=0\),即让 \(a_{1 \sim n}\) 均 \(=b_1\)。
在 \(b\) 数组中选择任意两数,有以下 \(3\) 种有效操作:
- 选择 \(b_i\) 与 \(b_j\),满足 \(2 \le i,j \le n\)。若 \(b_i\) 与 \(b_j\) 一正一负时,需要多进行此操作。
- 选择 \(b_1\) 与 \(b_i\),满足 \(2 \le i \le n\)。
- 选择 \(b_i\) 与 \(b_{n+1}\),满足 \(2 \le i \le n\)。
记 \(a\) 数组中正数之和为 \(p\),负数之和为 \(q\),则操作 \(1\) 需要进行
\(\min(p,q)\) 次,余下 \(\mid p-q \mid\) 未配对,则继续执行 \(2,3\) 类操作,共需 \(\max(p,q)\) 次操作。
而 \(\mid p-q \mid\) 次 \(2,3\) 类操作将会产生 \(\mid p-q \mid + \ 1\) 种不同的 \(b_1\) 值,于是就会产生 \(\mid p-q \mid + \ 1\) 种不同的序列。
code。
【例题 \(\texttt{14}\)】 \(\text{Tallest Cow}\)
建立一个数组 \(C\) 表示 \(N\) 头牛与最高牛的差距,初始值为全 \(0\)。
则对于每一对关系 \((A_i,B_i)\),我们将 \(C_{A_i+1 \sim B_i-1}\) 均 \(-1\),表示 \(A_i \sim B_i\) 中间的数均至少比它们少 \(1\)。
最后对于第 \(i\) 头牛,它的最大身高即为 \(h+C_i\)。
这样操作的复杂度是 \(O(nm)\) 的,显然不能支持。
于是,我们考虑求出 \(C\) 数组的差分数组 \(D\),这样每次区间 \(-1\) 的操作均可转化为将 \(D_{A_i+1}-1\) 并且将 \(D_{B_i}+1\)。输出时求出 \(D\) 数组的前缀和便得到了 \(C\) 数组。
时间复杂度便降至 \(O(n)\),可以接受。
注意此题关系可能重复,需要用一个 map
维护某个关系是否被访问过。
code。
\(\texttt{0x04}\) 二分
-
几个有用的
link
:
【例题 \(\texttt{15}\)】 \(\text{Best Cow Fences}\)
考虑二分答案,二分平均值,判定“是否存在一个长度不小于 \(f\) 的子段,使得其平均值不小于 \(mid\)”。
将子段中的每个数均 \(- \ mid\),判定方法又变为“是否存在一个长度不小于 \(f\) 的子段,使得其子段和非负”。
子段 \([i,j]\) 之和则可以表示为前缀和相减的形式,即:
\( \mathop{\max}\limits_{f \le i \le n} \{ sum_i - \mathop{\min}\limits_{0 \le j \le f} \{ sum_j \} \} \)
于是我们可以进行实数二分,对于每个 \(mid\),求出 \(A\) 数组 \(- \ mid\) 后的数组 \(B\),并且求出 \(B\) 数组的前缀和数组 \(C\)。
接着从 \(f\) 枚举到 \(n\),维护变量 \(mnx\) 记录 \(\mathop{\min}\limits_{0 \le j \le f} \{ sum_j \}\),\(ans\) 记录 $ \mathop{\max}\limits_{f \le i \le n} { sum_i } - mnx$。每轮循环依次更新 \(mnx\) 与 \(ans\)。
最后,若 \(ans>0\),则锁定右区间,否则锁定左区间。
二分结束后,直接输出 \(\lfloor r \times 1000 \rfloor\) 即可,记得强制类型转换!
code。
【例题 \(\texttt{16}\)】 \(\text{Innovative Business}\)
简单交互题。
对于每次询问,二分查找第一个比 \(k\) 大的位置并插入到其前面即可,最多 \(N \log N\) 次询问,可以接受。
也可使用 stable_sort
原地归并排序实现,更为简洁。
code(实际提交仅需 class
部分)。
\(\texttt{0x05}\) 排序
【例题 \(\texttt{17}\)】 \(\text{Cinema}\)
首先用一个 \(d\) 数组存下所有珂学家、语音、以及字母的语言,并对其进行离散化操作,再使用 \(cnt\) 数组统计出每个珂学家的语言出现的次数。
接着遍历每一部电影,分别求出它的语音和字幕在珂学家的语言中出现的次数,并与上一电影进行比较、更新答案即可。
code。
【例题 \(\texttt{18}\)】 货仓选址
直接对 \(A_{1 \sim N}\) 排序并取中位数即为答案。
-
\(\texttt{if } 2 \mid N \ , \ ans=A_{N/2}\);
-
\(\texttt{otherwise} \ , \ ans=A_{(N+1)/2}\)。
code。
【例题 \(\texttt{19}\)】 七夕祭
首先进行有解性判断。
显然要使每行 \(\text{cl}\) 喜爱的摊点数相同,必须使 \(t \mid n\);同理,要使每列喜爱的摊点数相同,则必须使 \(t \mid m\),我们据此便可完成有解性判断。
考虑如何求出最小交换次数,我们首先令 \(a_i\) 表示每行 \(/\) 列的 \(\text{cl}\) 最喜爱的摊点数,\(s_i\) 表示 \(\sum^{i}_{j=1} a_i\)。
若场地不是环形的,则最小交换次数为:
\( \sum^{m}_{i=1} |\dfrac{t}{m} \times i - s_i| \)
(此公式含义为:前 \(i\) 行 \(/\) 列最初共有 \(s_i\) 个 \(\text{cl}\) 最喜爱的摊点数,期望共拥有 \(\dfrac{t}{m} \times i\) 个 \(\text{cl}\) 最喜爱的摊点数,需要进行两者之差的多退少补操作。)
回到本题,场地是环形的,则可以想到从环上任意一个摊点断开成链,再转化成上面的问题。
若在第 \(k\) 个摊点上断环成链,则这 \(m\) 行 \(/\) 列所拥有的 \(\text{cl}\) 喜爱的摊点数与前缀和分别为:
\(
a_{k+1} \ \ \ s_{k+1}-s_k
\)
\(
a_{k+2} \ \ \ s_{k+2}-s_k
\)
\(
\cdot \cdot \cdot
\)
\(
a_{1} \ \ \ s_1+s_m-s_k
\)
\(
a_{2} \ \ \ s_2+s_m-s_k
\)
\(
\cdot \cdot \cdot
\)
\(
a_k \ \ \ s_k+s_m-s_k
\)
由上表可知,环上最小步数为:
\( \sum^{m}_{i=1}|s_i-s_k| \)
其中 \(s_k\) 选中位数时,上式最小。
于是我们对于行与列分别求出最小步数,累加起来就得到了答案。
code。
【例题 \(\texttt{20}\)】 \(\text{Runnig Median}\)
很容易想到一种朴素做法:维护一个 vector
,对于每个数,二分查找它应该处在的位置(STL upper_bound
),插入进去,当数字个数为奇数时,输出第 \(\frac{N-1}{2}\)项(\(N\) 为当前序列长度,因为是 \(0\) 下标)即可。该算法的时间复杂度为 \(O(N^2)\) 不可接受。
俗话说:脑子不够,暴力数据结构来凑。于是我们想到了一种对顶堆做法。具体实现如下:
-
建立两个优先队列,一个大根堆,一个小根堆。
-
维护这两个队列,使其始终保持以下性质 \(^{[1]}\):
-
序列中从小到大排名为 \(1 \sim \frac{N}{2}\) 的数放在大根堆;
-
剩下的(即从小到大排名 \(\frac{N}{2}+1 \sim N\) 的)以及中位数放在小根堆。
-
-
根据上述性质可知,小根堆中的元素个数应当总比大根堆多 \(1\)。
-
由这一结论,我们对于每一个输入的数,便可分两种情况讨论:
-
若小根堆为空,则优先放入小根堆。
-
否则,将其与小根堆的堆顶比较,若大于则进入小根堆,小于则进入大根堆。接着分别更新大、小根堆,使其符合性质 \([1]\)。
-
至此,本题以 \(O(n \log n)\) 的复杂度被完美解决。
code。
【例题 \(\texttt{21}\)】 \(\text{Ultra-QuickSort}\)
典中典。
【例题 \(\texttt{22}\)】 奇数码问题
梅开二度。
将两种状态的所有数存入 \(a,b\) 数组,并分别求出逆序对个数,若奇偶性相同则可以转换,否则不行。code。
- 多测不清空,\(\texttt{\_\_\_\_\_\_\_\_\_\_}\)。
\(\texttt{0x06}\) 倍增
【例题 \(\texttt{23}\)】 \(\text{Genius ACM}\)
要让“校验值”最大,则取的 \(M\) 对数必须为最大、最小,次大、次小......以此类推。
为使 \(A\) 数组分段最少,则可以从头开始分,每段的“校验值”在不超过 \(T\) 的情况下越长越好。
于是问题变成了:“在确定了一个左端点 \(L\) 以后,右端点 \(R\) 保证在 \(L \sim N\) 的区间中且区间 \(L \sim R\) 的校验值不超过 \(T\) 的情况下,最大能取到什么位置。
很容易想到二分的方法,但复杂度无法接受。于是考虑采用倍增。
倍增的方法如下:
-
初始令 \(p=1,R=L\)。
-
求出 \(L \sim R+p\) 的“校验值”,若 \(<T\) 则 \(R \gets R+p,p \gets p \times 2\),否则 \(p \gets p \div 2\)。
-
重复上一步骤,直到 \(p=0\),此时 \(R\) 即为所求。
以上方法的时间复杂度为 \(O(\log n)\),而计算“校验值”需要进行排序,时间复杂度为 \(O(n \log n)\),总时间复杂度为 \(O(n \log ^2 n)\),仍然不可接受。
于是我们想到仅将先加入的元素排序,在将原数组与新元素合并。利用归并排序,便可实现这一操作,时间复杂度降至 \(O(n \log n)\),完美过掉此题!
code。
\(\texttt{0x07}\) 贪心
- 贪心算法的正确性需要证明,以下题目的证明限于篇幅所以均略过。
【例题 \(\texttt{24}\)】 \(\text{Sunscreen}\)
此题可以抽象为:“给定 \(C\) 个区间以及 \(L\) 种点的坐标与个数,规定每个区间仅能放一个点,且这个点的坐标必须在区间之内,求将这些点最多能放入多少个区间”。
于是,我们对区间按右端点排序,并对这些点按坐标排序。接着对于每一个区间,遍历每一个点,判断其是否能放进此区间,能放则放,不放则已。时间复杂度为 \(O(n^2)\)。
注意区间和点必须使用 struct/pair
存储。
code。
【例题 \(\texttt{25}\) 】 \(\text{Stall Reservations}\)
首先按吃草时间对牛排序。
接着维护一个数组 \(S\),记录下每个畜栏安排进去的最后一头牛。
对于每一头牛,遍历 \(S\) 数组,找到 \(\ge\) 最后一头牛吃草时间的畜栏,插入进去,否则为其新建一个畜栏。
这样的时间复杂度为 \(O(n^2)\),若 \(S\) 数组改用优先队列,则可将时间复杂度降至 \(O(n \log n)\),完美通过。
code。
【例题 \(\texttt{26}\)】 \(\text{Radar Installation}\)
首先遍历每个岛屿,根据第 \(i\) 个岛屿的坐标 \(x_i,y_i\) 计算出它被控制的最大范围的左端点 \(l_i\) 与右端点 \(r_i\)。具体公式:
\( \begin{cases} k_i=\sqrt{d \times d - y_i \times y_i}\\ l_i=x_i+k_i\\ r_i=y_i+k_i \end{cases} \)
(其中 \(d\) 指雷达的覆盖半径,\(k_i\) 是临时变量。)
接着按照左端点排序。令 \(pos\) 初始为 \(-\infty\),对于第 \(i\) 个雷达,若 \(pos < l_i\),则令 \(pos=r_i\),否则令 \(pos=\min(pos,r_i)\)。
code。
【例题 \(\texttt{27}\)】 国王游戏
按大臣左手上的数 \(l\) 与右手上的数 \(r\) 的乘积排序,接着在所有人中找到奖赏最多的即可。
需要用高精度,所以不贴代码了。。。
【例题 \(\texttt{28}\)】 \(\text{Color a Tree}\)
题解link(未过审 \(qwq\))。
\(\texttt{0x08}\) 第一章练习
【习题 \(\texttt{1}\)】 \(\text{The Pilots Brother's Refrigerator}\)
枚举 \(0 \sim 2^{16}\) 中的所有二进制数,依次取出每一位,若此位为 \(1\),则按一下开关。
所有位都取完后,遍历整个 \(4 \times 4\) 的矩形,检验所有开关均为开启状态。若是,则比较步数,若当前步数 \(<\) 上一方案的步数,更新答案。
可以在每次按下开关时用一个 pair
类型的数组记录下开关的 \((x,y)\),最后输出这个数组即可。
坑点:
-
多测要清空。
-
按下开关等价于改变当前开关 \((x,y)\) 的状态,并且改变 \(x\) 行和 \(y\) 列的所有开关(包括当前开关)。
code。
【习题 \(\texttt{2}\)】 占卜 \(\text{DIY}\)
没啥好说的,直接开一个 deque
模拟所有操作即可。
建议写个 get
函数将扑克牌转换为数字。
code。
【习题 \(\texttt{3}\)】 \(\text{Practal}\)
一图胜千言。
转自 https://www.acwing.com/solution/content/823/
注意递归边界。
code。
【习题 \(\texttt{4}\)】 \(\text{Raid}\)
被卡了一周的题
不难看出,此题其实是求平面上两组点的最近点对。
于是我们先考虑一种简单情况:只有一组点。
这种情况可以使用分治算法,分别计算出 \([l,mid]\) 以及 \([mid+1,r]\) 的答案,合并即可。计算过程如下:
-
将 \([l,r]\) 中所有距离小于当前答案的点存入 \(a\) 数组中;
-
按照 \(x\) 坐标对 \(a\) 数组排序;
-
对于 \(a\) 数组中的任意两点,若它们的 \(y\) 坐标 \(<\) 当前答案,计算出它们之间的距离,对于所有这样的距离取 \(\min\) 就是答案。
时间复杂度 \(O(n \log ^2 n)\),使用归并排序可以降至 \(O(n \log n)\)。
回到本题,考虑增加一组点后,在什么情况下会影响答案。
显然,选择一个原来的点,若存在一个新点,它们之间的距离 \(<\) 当前答案,则会更新答案。
于是我们可以先对于所有点按 \(x\) 轴排序,接着对于所有原来的点,以这些点为圆心,当前答案为半径画一个圆,将其中的新点个数存入 \(a\) 数组中。
处理 \(a\) 数组中的所有点对,若它们的 \(y\) 坐标 \(<\) 当前答案,计算出它们之间的距离,对于所有这样的距离取 \(\min\) 就是答案。
若使用归并排序,则时间复杂度还是 \(O(n \log n)\)。
结果这个被 \(\text{hack}\) 了。。。
于是,我们使用了 \(zky\) 大佬的人类智慧。
首先,对所有点按照 \(x \times y\) 排序。接着进行 \(5\) 次随机旋转,接着在 \(2n\) 个点中选出前 \(100\) 个点对,对于所有的类型不同的点对,计算出它们之间的距离,对于所有这样的距离取 \(\min\) 就是答案。
时间复杂度约为 \(O(n)\)。
code。
【习题 \(\texttt{5}\)】 防线
题意简述:给定 \(n\) 个起点为 \(s\)、终点为 \(e\)、公差为 \(d\) 的等差数列,求奇数位的位置。
二分答案。首先需要求出最小的起点和最大的终点,分别记为 \(minx\) 和 \(maxx\)。
考虑如何设计 check
函数。我们可以通过求出某个区间的数字和,判断它的奇偶性来确定应该前往哪个区间。
如何求出某个区间的数字和?可以枚举所有的等差数列,计算包含在此区间的所有数列的和即可。
code。
【习题 \(\texttt{6}\)】 \(\text{Corral the Cows}\)
依然是二分答案。依然是考虑如何设计 check
函数。
使用双指针法。建立两个指针 \(i\)、\(j\),对于所有 \(1 \sim n\) 的 \(i\),找到最大的且与 \(a_i\) 的 \(x\) 坐标不超过 \(mid\) 的 \(j\)。
遍历 \(i \sim j\),记当前的数为 \(k\),则 \(y\) 坐标范围在 \(a_{k_y} \sim a_{k_{y+mid}}\) 的点的个数即为三叶草个数。
code。
【习题 \(\texttt{7}\)】 糖果传递
首先令 \(x_i\) 表示每个小朋友向左边传递的糖果数,\(avg\) 表示每个小朋友应有的糖果数。则有:
\( \begin{cases} avg=a_i-x_i+x_{i+1} \ (1 \le i < n) \\ avg=a_n-x_n+x_1 \ (i = n) \end{cases} \)
变形得:
\( x_i=avg \times i-\sum_{i=1}^{n}a_i+x_1 \)
这是我们再令 \(c_i\) 表示:
\( \sum_{i=1}^{n}a_i-avg\times i \)
则有:
\( x_i=x_1-c_i \)
要使代价最小,则需要使 \(x_i\) 最小。而 \(c_i\) 是可以预处理出来的,因此 \(x_1\) 必须最小。
根据“货仓选址”的知识,我们知道,\(x_1\) 选取中位数时最小。
于是我们可以预处理出 \(c_i\),接着对 \(x_i\) 排序,取中位数计算答案即可。时间复杂度 \(O(n)\)。
注意开 long long
。
code。
【习题 \(\texttt{8}\)】 \(\text{Soldiers}\)
既然要令所有士兵的位置离目标 \((x,y)\) 距离最近,则 \(y\) 均取中位数即为最优 \(y\) 坐标。
对于任意的 \(x_i\),有 \(x_i=x_0+i-1\),则取 \(x_i-i+1\) 的中位数 \(x_0\) 即为最优 \(x\) 坐标。
code。
【习题 \(\texttt{9}\)】 \(\text{Number Base Conversations}\)
很容易想到的思路便是将 \(n\) 进制先转换为 \(10\) 进制,在转换为 \(m\) 进制。
具体实现:
-
先写一个
getN
函数将 \(n\) 进制转为十进制,存入 \(a\) 数组; -
观察进制转换的过程可知,一个 \(n\) 进制数的每一位 \(x\bmod m\) 的值即为它在 \(m\) 进制下的值。根据这一结论,便可遍历 \(n\) 进制数的每一位数,求出余数,从而构建出对应的 \(m\) 进制数,并存入栈
stk
中。 -
去除前导 \(0\),并输出倒序输出栈
stk
即可。
code。
【习题 \(\texttt{10}\)】 \(\text{Cow Acrobats}\)
依照国王游戏的思路,对所有牛按 \(x+y\) 排序,接着算出所有牛的风险值取 \(\min\) 即为答案。
code。
【习题 \(\texttt{11}\)】 \(\text{To the Max}\)
首先,对于矩阵的每一行计算出前缀和。
接着枚举 \(1 \sim n\) 中的两列,继续枚举这两列中间的所有行,令 \(sum\) 不断累加前缀和,同时更新全局最优答案。
code。
【习题 \(\texttt{12}\)】 \(\text{Task}\)
贪心策略:按照时间从大到小的顺序排序任务与机器,并在时间充足的机器中选择等级最小的。
于是我们在排序后,倒序处理每个任务,并且将时间充足的机器加入一个可重集合,并用 lower_bound
求出等级 \(\ge\) 当前任务等级且最小的机器。
code。
\(\texttt{0x09}\) 栈
【例题 \(\texttt{29}\)】 \(\text{Push,Pop,GetMin}\)
rt,此题就是要我们维护一个栈,来实现入栈、出栈以及取栈中最小值这几个操作。
普通的栈是无法满足要求的,这时我们便想到用两个栈来解决问题。其中一个栈 \(a\) 保存原始数据,另一个栈 \(b\) 则保存当前栈中的最小值。
具体实现如下:
-
对于
Push
操作,将读入的 \(x\) 压入栈 \(a\),设当前 \(b\) 栈顶为 \(y\),则将 \(\min(x,y)\) 压入栈 \(b\)。 -
对于
Pop
操作,同时弹出栈 \(a\) 与栈 \(b\) 的栈顶。 -
对于
GetMin
操作,直接输出 \(b\) 栈栈顶。
这样的做法使得每个操作都做到了 \(O(1)\) 的时间复杂度,完美通过本题~
code。
【例题 \(\texttt{30}\)】 \(\text{Editor}\)
很容易想到用两个栈维护这个编辑器,一个栈 \(a\) 维护开头到光标处的数字,另一个栈 \(b\) 维护光标处到结尾的数字。
同时维护两个数组 \(s,f\),分别表示当前的前缀和与最大前缀和。
具体的(记光标位置为 \(p\)):
-
I x
:将 \(x\) 压入 \(a\) 中,并且令 \(s_p \gets s_{p-1}+x,f_p \gets \max(f_{p-1},s_p)\)。 -
D
:若 \(a\) 非空,则令 \(a\) 弹出栈顶。 -
L
:若 \(a\) 非空,则令 \(a\) 弹出栈顶,并将其压入 \(b\) 中。 -
R
:若 \(b\) 非空,则令 \(b\) 弹出栈顶,并将其压入 \(a\) 中;同时令 \(s_p \gets s_{p-1}+x,f_p \gets \max(f_{p-1},s_p)\)。 -
Q k
:直接输出 \(f_k\)。
code。
【例题 \(\texttt{31}/\texttt{32}\)】 进出栈序列问题
解法一:dfs
,\(O(2^n)\)。
- 用于输出所有方案。code。
解法二:递推,\(O(n^2)\)。
-
所有数的进出站过程如下:
-
将 \(1\) 进栈;
-
将 \(2 \sim k\) 共 \(k-1\) 个数出栈;
-
将 \(1\) 出栈,排在第 \(k\) 个;
-
将 \(k+1 \sim n\) 共 \(n-k\) 个数进出栈。
-
-
于是可以得到递推公式(记 \(n\) 个数的方案数为 \(s_n\)):
\( S_n=\sum^{n}_{k=1} S_{k-1} \times S_{n-k} \)
解法三:Catalan
,\(O(n)\)。
- 计算公式:
\( \dfrac{\binom{2n}{n}}{n+1} \)
\(\texttt{0x10}\) 单调栈
好久没更笔记了 \(qwq\)。
【例题 \(\texttt{33}\)】 \(\text{Largest Rectagle in a Hisgoram}\)
一个显而易见的思路便是,枚举每个矩形,尝试将其高度作为最大矩形的高度,并尽量向右边界扩展,对于所有这样得到的矩形面积取 \(\max\) 即为答案。
稍作思考便可发现,对于一个高度 \(<\) 上一矩形高度的矩形,若以它的高度为最大矩形高度,则其他矩形比它高的部分均毫无用处。
于是我们就可以想到用一个高度为当前矩形高度,宽度为超出部分累加的一个矩形替代,这样就保证了所有矩形均是单调递增的。
进而我们就得到了一个可行的实现:
-
维护一个栈,存放所有矩形,高度单调递增。
-
枚举所有矩形,若当前矩形高度 \(>\) 栈顶矩形高度,则直接入栈。
-
否则,不断弹出栈顶直到栈顶矩形高度 \(<\) 当前矩形高度或栈为空,其间不断累加宽度与答案,最后将一个高为当前矩形高度,宽为宽度累加之和的矩形入栈。
code。
\(\texttt{0x11}\) 队列
【例题 \(\texttt{34}\)】 \(\text{Team Queue}\)
建立 \(n+1\) 个队列,其中第 \(0\) 个保存每个小组的编号,第 \(1 \sim n\) 个保存每个小组的成员。
当接收到入队指令时,直接将 \(x\) 插入进第 \(y\) 个队列的末尾,若插入前队列为空,则需要将 \(y\) 插入进第 \(0\) 个队列的末尾。
当接收到出队指令时,直接将 \(x\) 弹出第 \(y\) 个队列,若弹出后队列为空,则将 \(y\) 从第 \(0\) 个队列中弹出。
code。
【例题 \(\texttt{35}\)】 蚯蚓
闲话:\(q=0\) 的 \(set\) 做法都有人写挂了,是谁我不说。
考虑一种 \(\text{brute-force}\):
-
维护一个集合保存每条蚯蚓的长度,并且维护一个变量 \(delta\),记录集合中每个数的偏移量。
-
对于第一问,在第 \(s\) 秒 中,若 \(t \mid s\),取出集合中的最大值 \(x\),并输出 \(x+delta\)。
-
将 \(\lfloor px \rfloor - delta - q\) 和 \(x-\lfloor px \rfloor -delta -q\) 插入集合中。
-
令 \(delta \gets delta+q\)。
-
对于第二问,将集合改为大根堆,在第 \(s\) 秒中,若 \(t \mid s\),则输出堆顶 \(+ \ delta\)。
这样的复杂度是 \(O(m \log n)\) 的,由于 \(m\) 的数据范围过大,因此是不可接受的。
于是我们推了一下式子,发现从堆中取出的数和新产生的数都是单调递减的。\(dbxxx\) 大佬的证明。
这样我们就可以建立三个大根堆 \(A,B,C\),分别维护原始数据以及新产生的两个数,因为 \(A,B,C\) 均是单调递减的,所以最大值一定在 \(A,B,C\) 的堆顶之一。配合上面的思路,可以做到 \(O(m+n \log n)\),完美过掉~
code。
【例题 \(\texttt{36}\)】 双端队列
反向考虑将数组排序后,对应着几个双端队列。
建立一个 \(B\) 数组,存储 \(A\) 数组的下标,并对 \(A\) 数组排序。
稍加分析便可发现,\(B\) 数组中的若干个单谷段就对应着 \(A\) 数组的入队顺序(谷点第一个入队,递减的从队头入队,递增的从队尾入队)。
于是我们可以按照 \(A\) 数组中各个数据的异同来给 \(B\) 分段,然后统计 \(B\) 中单谷段的数量即为答案。
code。
\(\texttt{0x12}\) 单调队列
- 如果有人比你小,还比你强,那你就永远超不过 \(ta\) 了——\(cz\)。
【例题 \(\texttt{37}\)】 最大子序和
为什么只有一道例题啊 \(qwq\)。
建立一个队列,保存“下标位置递增,且前缀和值也递增的”最优决策集合下标。
循环 \(1 \sim n\),对于每一个满足 \(1 \le i \le n\) 的 \(i\),若队首元素与 \(i\) 的决策范围超过 \(m\),则不断弹出队头。
此时队头就是最优答案,更新全局答案。
接着将队尾所有前缀和 \(<i\) 的元素全部出队。
这便是著名的单调队列算法,时间复杂度 \(O(n)\)。
code。
链表那一章有心情再补。
\(\texttt{0x13}\) \(\text{Hash}\)
\(\text{Hash}\) 表可以看成是一个 vector
\(+\) 链表的组合。
它主要支持如下操作:
- 对于某个元素,可以根据其 \(\text{Hash}\) 函数值确定它所对应的链表,并执行遍历、比较、插入操作。
\(\text{Hash}\) 表的期望时间复杂度近似为 \(O(n)\),其中访问是 \(O(1)\) 的。
【例题 \(\texttt{38}\)】 \(\text{Snowflake Snow Snowflake}\)
首先,需要依次将这 \(n\) 片雪花插入 \(\text{Hash}\) 表中。同时定义 \(\text{Hash}\) 函数为
\( H(a_{i,1},a_{i,2},...,a_{i,6})=\sum^{6}_{j=1} a_{i,j}+\prod^{6}_{j=1} a_{i,j} \)
对于每一对雪花,我们依次顺时针和逆时针将它们遍历一次,计算 \(\text{Hash}\) 函数值,若顺时针或逆时针遍历的结果完全一致,则说明这两片雪花相同。
code。
\(\texttt{0x14}\) 字符串 \(\text{Hash}\)
这种算法主要是将字符串映射为一个整数。
我们考虑,取一个固定值 \(P\),将字符串看作是 \(P\) 进制数,并且给每个字符分配一个 \(\ge 0\) 的数值(例如 \(a=1,b=2,...,z=26\))。此时再取一个固定值 \(M\),这个 \(P\) 进制数 \(\mod M\) 的值即为这个字符串的 \(\text{Hash}\) 值。
一般来说,我们习惯取 \(P=131,13331\),\(M=2^{64}\)。
于是,对于字符串的各种操作,便可以转换成整数操作:
-
若我们已知字符串 \(S\) 的 \(\text{Hash}\) 值,现需要在 \(S\) 后接上一个字符 \(c\),则接上后的 \(S\) 的 \(\text{Hash}\) 值为 \(H(S) \times P +value_c) \bmod m\),其中 \(value_c\) 表示分配给字符 \(c\) 的值。
-
若我们已知字符串 \(S\) 的 \(\text{Hash}\) 值,且已知字符串 \(S\) 后接字符串 \(T\) 的 \(\text{Hash}\) 值,则字符串 \(T\) 的 \(\text{Hash}\) 值为 \(H(S+T)-H(S) \times P^{len(T)}\),其中 \(len(T)\) 表示字符串 \(T\) 的长度。
【例题 \(\texttt{39}\)】 兔子与兔子
有了前面的铺垫,这题的思路就十分明了了。
首先,根据公式 \(H(S+c)=H(S) \times P +value_c) \bmod m\),可以 \(O(n)\) 地预处理出 \(\text{DNA}\) 序列所有前缀的 \(\text{Hash}\) 值,保存在 \(f\) 数组中。
同时,我们也需要预处理出 \(P\) 的若干次幂的值,保存在 \(p\) 数组中。
对于每个询问,根据公式 \(H(T)=H(S+T)-H(S) \times P^{len(T)}\),则区间 \([l,r]\) 的 \(\text{Hash}\) 值即为 \(f_r-f_{l-1} \times p_{r-l+1}\)。
因此仅需比较 \(f_{r_1}-f_{l_1-1} \times p_{r_1-l_1+1}\) 是否等于 \(f_{r_2}-f_{l_2-1} \times p_{r_2-l_2+1}\) 即可 \(O(1)\) 地回答每个询问。
时间复杂度 \(O(len(S)+Q)\)。
code。
【例题 \(\texttt{40}\)】 \(\text{Palindrome}\)
我们知道,回文串分为两类,分别是奇回文串和偶回文串。
于是,我们分别考虑两种回文串:
-
对于奇回文串,二分 \(p\) 的值,若该回文串以 \(i\) 为中心,则需要使得 \(S_{i-p \sim i} = rev(S_{i \sim i+p})\),其中 \(rev(S)\) 表示将字符串 \(S\) 倒置。此时该回文串的长度为 \(2 \times p + 1\)。
-
对于偶回文串,二分 \(q\) 的值,若该回文串以 \(i\) 和 \(i-1\) 之间的夹缝为中心,则需要使得 \(S_{i-q \sim i-1} = rev(S_{i \sim i+q})\)。此时该回文串的长度为 \(2 \times q\)。
对于字符串的倒置比较,可以分别预处理出一个字符串 \(S\) 的前缀和后缀的 \(\text{Hash}\) 值,从而实现 \(O(1)\) 的进行比较操作。
时间复杂度为 \(O(n \log n)\),有种名为 \(\text{Manacher}\) 的算法能够 \(O(n)\) 解决此问题。由于笔者太菜所以虽然学过但并未学懂
code。
【例题 \(\texttt{41}\)】 后缀数组
首先定义 \(SA\) 数组,初始 \(SA_i=i\)。
对 \(SA\) 数组进行排序。对于两个相邻下标 \(a,b\),二分它们最长公共前缀的长度,利用预处理出的 \(\text{Hash}\) 值 \(O(1)\) 地进行判断当前前缀是否合法,时间复杂度 \(O(n \log^{2} n)\)。
输出 \(SA\) 数组后,循环 \(2 \sim n\),输出所有下标为 \(i\) 与 \(i-1\) 的最长公共前缀的长度即可。
code。
\(\texttt{0x15} \ \text{KMP}\)
\(\text{KMP}\) 算法能够实现在线性时间内判定字符串 \(A\) 是否为 \(B\) 的子串,并且求出字符串 \(A\) 在 \(B\) 中出现的位置。
\(\text{KMP}\) 算法的核心就是 \(next\) 数组,它可以帮助字符串 \(A\) 完成“自我匹配”。具体的,\(next_i\) 的定义为:\(A\) 中以 \(i\) 结尾的非前缀子串和 \(A\) 的前缀的最大匹配长度。即:
\( next_i=\max{\{j\}} \ (j < i \ \text{且} \ A_{i-j+1 \sim i}=A_{1 \sim j}) \)
特别的,若不存在这样的 \(j\),则令 \(next_i=0\)。
同时,若某个整数 \(j\) 满足 \(j < i \ \text{且} \ A_{i-j+1 \sim i}=A_{1 \sim j}\),则我们称其为 \(next_i\) 的候选项。
如何在线性时间内求出 \(next\) 数组?这里有一个引理:
- 若 \(j_0\) 是 \(next_i\) 的一个候选项,则 \(< j_0\) 的最大的 \(next_i\) 的候选项为 \(next_{j_0}\)。
根据引理,若计算出了 \(next_{i-1}\),则它的候选项从大到小依次为 \(next_{i-1},next_{next_{i-1}},next_{next_{next_{i-1}}}......\);又由于若 \(j\) 是 \(next_i\) 的候选项,则 \(j-1\) 必定是 \(next_{i-1}\) 的候选项,则 \(next_i\) 的候选项从大到小依次为 \(next_{i-1}+1,next_{next_{i-1}}+1,next_{next_{next_{i-1}}}+1......\)。
综上,我们便可写出求 \(next\) 数组的流程了:
-
初始化 \(next_i=j=0\)。
-
从 \(i=2\) 开始循环,其间不断尝试扩展长度 \(j\),若失败(下一字符不相等)则令 \(j=next_j\),直至 \(j=0\)(应从头开始)。
-
若扩展成功,则令 \(j=j+1\),此时 \(next_i=j\)。
code。
【例题 \(\texttt{42}\)】 \(\text{Period}\)
又一个引理:
- \(S_{1 \sim i}\) 具有长度为 \(len\) 的循环元的充要条件是 \(len < i\) 且 \(len \mid i\) 且 \(S_{len+1 \sim i} = S_{1 \sim i-len}\)。
根据引理,当 \(i-next_i\) 能整数 \(i\) 时,\(S_{1 \sim i-next_i}\) 是 \(S_{1 \sim i}\) 的循环元,它的循环次数为 \(\dfrac{i}{i-next_i}\)。
进一步的,根据 \(next\) 数组的引理,我们同样可以求出 \(S\) 的所有循环元。
code。
\(\texttt{0x31} \ 质数\)
其他的什么埃氏筛、试除法之类应该都会,此处不再赘述,重点是要记住这个结论:对于任意合数 \(n\),其必定有一个不超过 \(\sqrt{n}\) 的(质)因子。反证法易证。
然后讲一下线性筛,其基本原理就是根据唯一分解定理,对于每个合数从大到小地累积质因数,从而使得每个合数只有唯一的一种方式被凑出,将时间复杂度降至 \(O(n)\)。
在每个合数被筛到之前,它的质因子一定全部被筛到了,所以正确性是显然的。
【例题 \(\texttt{43}\)】\(\text{Prime Distance}\)
记住一个区间筛技巧:对于闭区间 \([l,r]\),可以筛出 \(2 \sim \sqrt{r}\) 的所有质数,然后把 \([l,r]\) 中能被它们任意一个整除的数标记为合数,剩余的即为质数了。这里运用的就是开头提到的结论。
然后这题就被解决了。时间复杂度 \(O(\sqrt{r}+(r-l) \times \log \sqrt{r})\)。
code。
总结:
-
记住上述套路。
-
不要用
map
,学会数组平移,注意多测尽量少清空。 -
考虑边界情况。
【例题 \(\texttt{44}\)】 阶乘分解
1s 1e9,LG测评机有点过于强大了。
显然暴力的 \(O(n \sqrt{n})\) 无法承受。考虑优化。
从质因子下手,显然 \(n!\) 的所有质因子都是 \(1 \sim n\) 的所有质数,先将 \(1 \sim n\) 的质数筛出来。
对于一个质数 \(p(1 \le p \le n)\),\(n!\) 中包含它的个数就是 \(1 \sim n\) 中包含它的个数的总和(包括包含一个它的、两个它的......)。于是运用容斥原理可知这个值是:
\( \lfloor \frac{n}{p} \rfloor + \lfloor \frac{n}{p^2} \rfloor + ... + \lfloor \frac{n}{p^{\lfloor \log_p n \rfloor}} \rfloor = \sum_{p^k \le n} \lfloor \frac{n}{p^k} \rfloor \)
因为一共 \(\frac{n}{\log n}\) 个质数,每个质数需要 \(\log n\) 的时间计算上式,于是时间复杂度为 \(O(n)\)。
code。
总结:
-
暴力优化考虑缩小范围、转换角度。
-
学会推式子。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】