FFT 学习笔记
FFT 学习笔记
\(\mathbf{Preview}\)
前置知识。
多项式表示法
系数表示法:就是正常的多项式表示方法。\(f(x) = \sum\limits_{i = 0}^{n - 1} a_i \times x^i\)。
点值表示法:如果我们知道 \(n\) 个数 \(b_1, \cdots, b_n\),这些数在带入 \(f\) 多项式后他们的具体值 \(f(b_1), \cdots, f(b_n)\),我们就可以通过这些值算出 \(f\),因此 \(f\) 也可以用若干个点对 \(\Big(b_1, f(b_1)\Big),\cdots,\Big(b_n, f(b_n)\Big)\) 来表示。1
向量
向量是一个有方向,有长度的量,为了方便运算,起点坐标通常被设为原点。
但正如同刚才所说,向量的两个关键因素为方向和长度,所以还可以用这一种方式:
\]
这也被称为 极角坐标系, \(r\) 也被称为向量的模长,\(\alpha\) 自然是与 \(x\) 轴成的夹角。
向量在坐标系中大致是这样:

由于在 FFT 中不是很重要,所以粗略介绍。
弧度制
一个点在一个以原点为中心半径为 \(1\) 的圆上时,这个点与 \(x\) 轴的夹角在弧度制下被表示为这个点到圆与 \(x\) 轴的交点的圆弧长度。简单来说,\(2\pi = 360\degree\)。
复数
设 \(a,b\) 为实数,\(i^2 = -1\),则形如 \(a+bi\) 的数被称为复数。
在复平面中,\(x\) 轴表示实数,\(y\) 轴(除原点)表示虚数,从 \((0, 0)\to (a, b)\) 的向量表示 \(a+bi\)。
模长:表示 \(a+bi\) 向量的模长,即 \(\sqrt{a^2 + b^2}\)。
幅角:以逆时针为正方向,\(x\) 轴正方向到 \(a+bi\) 的向量形成的有向角。
加法运算:\((a+bi)+(c+di) = (a+c)+(b+d)i\)
减法运算:\((a+bi)-(c+di) = (a-c)+(b-d)i\)
乘法运算:
- 几何定义:复数相乘,模长相乘,幅角相加
- 代数定义:\((a+bi)\times (c+di) = (ac - bd) + (bc + ad)i\)。
除法定义在本文中不提及。
单位根
下文中,默认 \(n\) 为 \(2\) 的正整数次幂
以单位圆原点为起点,圆的 \(n\) 等分点为终点,做 \(n\) 个向量,设幅角为正且最小的向量对应的复数为 \(w_n\),称为 \(n\) 次单位根。
根据复数乘法的运算法则,其余 \(n - 1\) 个复数为 \(w_n^2, w_n^3,\cdots,w_n^{n}\)
注意 \(w_n^0 = w_n^n\)(对应复平面上以 \(x\) 轴为正方向的向量,就是图中的 \(u\))
由欧拉公式,\(w_n^k = \cos k\dfrac{2\pi}{n}+i\sin{k\dfrac{2\pi}{n}}\),且有 \(w_{2n}^{2k} = w_n^k,w_{n}^{k+\frac{n}{2}}=-w_{n}^k,w_n^0 = w_n^n = 1\)。
接下来进入正题。
\(\mathbf{Part. 0 \ \ 思路}\)
首先,我们考虑如何计算两个多项式 \(P, Q\) 的乘法,假设次数分别为 \(n - 1, m - 1\),为了方便后面的分治,我们先把 \(P, Q\) 的长度扩成一个大于 \(n + m\) 的 \(2\) 的幂次。设这个幂次为 \(l = 2^t\)。
显然,在系数表示法中,我们只能 \(\mathcal{O}(n^2)\) 求乘法。
但是,如果我们能把 \(P, Q\) 都转化成点值表示法,其中 \(P\) 的点值表示法为 \(\Big(x_1, P(x_1)\Big),\cdots,\Big(x_l, P(x_l)\Big)\),\(Q\) 的点值表示法为 \(\Big(x_1, Q(x_1)\Big),\cdots,\Big(x_l, Q(x_l)\Big)\)。然后,因为 \(P(x_i)\) 和 \(Q(x_i)\) 是两个多项式的结果,所以我们可以直接对每个 \(P(x_i) \times Q(x_i) = Z(x_i)\)。对于 \(\Big(x_1, Z(x_1)\Big),\cdots,\Big(x_l, Z(x_l)\Big)\),我们将点值表示法转化回系数表示法,就能得到 \(P \times Q\) 的值。
整合思路,我们需要将系数表示法转化为点值表示法,再将点值表示法转化回系数表示法。
第一步被称为 \(\mathbf{FFT}\),第二部被称为 \(\mathbf{IFFT}\)。
\(\mathbf{Part. 1 \ \ FFT}\)
\(\mathbf{FFT}\) 是为了解决如何快速将一个多项式从系数表示法转化为多项式表示法的。如果我们直接暴力计算,我们要对于 \(n\) 个数,每个数计算 \(f(x)\),总复杂度 \(\mathcal{O}(n^2)\)。(相关章节:\(\mathbf{Preview},多项式表示法\))
设我们现在想把 \(A(x) = \sum\limits_{i = 0}^{n - 1} a_i\times x^i\) 转化为点值表示法。
在 \(\mathbf{Preview}\) 中的 多项式表示法 中,由 <1>,我们知道一个多项式可以被 \(n\) 个点唯一确定。
假设最终的点值表示法是 \(\Big(x_1, f(x_1)\Big),\cdots,\Big(x_n, f(x_n)\Big)\),我们考虑将 \(x_1, \cdots, x_n\) 设为 \(w_n^0, \cdots, w_n^{n - 1}\),然后计算 \(f(w_n^0),\cdots,f(w_n^{n - 1})\)。所以我们只需要计算 \(A(w_n^0),\cdots,A(w_n^{n - 1})\)。2
考虑分治计算这个值。我们把 \(A(x)\) 按照下标奇偶性进行分类:
A(x) = & (a_0 + a_2 \times x^2 + \cdots + a_{n - 2} \times x^{n-2}) +\\
& (a_1 \times x^1 + a_3\times x^3 + \cdots + a_{n - 1}\times x^{n - 1})
\end{aligned}
\]
然后继续考虑构造 \(A_1, A_2\):
A_2(x) = a_1 + a_3 \times x + \cdots + a_{n - 1} \times x ^ {\frac{n}{2} - 1}
\]
那么综合 \((2)(3)\):
\]
由 <2> 知,我们需要把 \(w_n^k\) 代入多项式。
由于我们需要分治,考虑将 \(k\) 按照 \(k < \dfrac{n}{2}\) 和 \(k \geq \dfrac{n}{2}\) 考虑。
对于 \(k < \dfrac{n}{2}\):(接下来的化简如果不懂,可以尝试结合 \(\mathbf{Preview}\) 中的 单位根 来看)
A(w_n^k) & = A_1(w_n^{2k}) + w_n^kA_2(w_n^{2k})\\
& = A_1(w_{\frac{n}{2}}^k) + w_n^kA_2(w_{\frac{n}{2}}^k)
\end{aligned}
\]
对于 \(k\geq \dfrac{n}{2}\),考虑 \(k = k' + \dfrac{n}{2}\):
A(w_n^{k}) & = A_1(w_n^{2k' + n}) + w_n^{k' + \frac{n}{2}} A_2(w_{n}^{2k' + n})\\
& = A_1(w_n^{2k'} \times w_n^n) + w_n^{k'} w_n^{\frac{n}{2}} A_2(w_n^{2k'} \times w_n^n)\\
& = A_1(w_n^{2k'}) - w_n^{k'} A_2(w_n^{2k'})\\
& = A_1(w_{\frac{n}{2}}^{k'}) - w_n^{k'} A_2(w_{\frac{n}{2}}^{k'})
\end{aligned}
\]
我们发现,对于 \((5)\) 和 \((6)\) 的结果式,它们再 \(A_1\) 和 \(A_2\) 多项式中代入得值是一样的(都是 \(w_{\frac{n}{2}}^k\),其中 \(k < \frac{n}{2}\))。因此,如果我们分治求出来了 \(A_1(w_{\frac{n}{2}}^{k})\) 和 \(A_2(w_{\frac{n}{2}}^k)\),我们就可以通过 \((5)(6)\) 来算出来 \(k < \dfrac{n}{2}, A(w_n^k)\) 和 \(k \geq \dfrac{n}{2},A(w_n^{k})\),因此拼起来就是完整的 \(A(w_n^k)\)。
因此,我们只需要分治求出来 \(A_1(w_{\frac{n}{2}}^{k})\) 和 \(A_2(w_{\frac{n}{2}}^k)\),然后求出来 \(A(w_n^k)\),我们就可以得出 \(A\) 的点值表示法。对于每一层,我们只需要 \(\mathcal{O}(n)\) 的时间复杂度计算,总共 \(\mathcal{O}(\log n)\) 层,因此时间复杂度为 \(\mathcal{O}(n\log n)\)。
\(\mathbf{Part. 2 \ \ IFFT}\)
\(\mathbf{IFFT}\) 是为了解决如何快速将一个多项式从点值表示法转化为系数表示法的。(相关章节:\(\mathbf{Preview},多项式表示法\))
假设我们对于多项式 \(A(x) = \sum\limits_{i = 0}^{n - 1} a_i \times x^i\),求出来了 \(A\) 的点值表示 \(\Big(w_n^0, t_0\Big), \cdots, \Big(w_n^{n - 1}, t_{n - 1}\Big)\),则我们想要通过 \(t\) 数组来还原出 \(a\) 数组。
我们考虑构造数组 \(c\),使得 \(c_k = \sum\limits_{i = 0}^{n - 1} t_i \times(w_n^{-k})^i\)。这个式子的含义是,我们考虑对于 \(f(x) = \sum\limits_{i = 0}^{n - 1}t_i \times x ^ i\) 这个多项式,代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 计算 \(f(x)\)(也就是 \(t\) 数组)的点值表示法,得到 \(c_0, \cdots, c_{n - 1}\)。3
这里你可以这样理解:我们在对 \(A\) 求出代入 \((w_n^0, \cdots, w_n^{n - 1})\) 时的点值表示时,再对这个结果代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 时求出点值表示,最后得到 \(c\) 数组。
考虑对这个 \(c\) 数组进行变换:
c_k & = \sum_{i = 0}^{n - 1} t_i \times(w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1} \color{red}A(w_n^{i})\color{black}\times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1} \color{red}\sum_{j = 0}^{n - 1} a_j \times (w_n^i)^j\color{black}\times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times (w_n\color{red}^j\color{black})\color{red}^i\color{black} \times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times \color{red}(\color{black}w_n^j\times w_n^{-k}\color{red})^i\color{black}\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times (\color{red}w_n^{j - k}\color{black})^i\\
& = \color{red}\sum_{j = 0}^{n - 1}a_j \times \sum_{i = 0}^{n - 1}\color{black}(w_n^{j - k})^i
\end{aligned}
\]
我们考虑 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i\) 如何快速计算。容易发现这是一个等比数列的形式,则:
- \(j - k = 0\),则 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i = \sum\limits_{i = 0}^{n - 1}1^i = n\)。
- \(j = k \neq 0\),则由于等比数列的比为 \(w_{n}^{j - k}\),则 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i = \dfrac{(w_n^{j - k})^n - (w_n^{j - k})^0}{w_{n}^{j - k} - 1} = 0\)。
因此,我们发现,只有当 \(j = k\) 时,\(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i\) 为 \(n\),其余情况均为 \(0\)。因此,\((21)\) 结果式转化为:
\]
因此,\(a_k = c_k \times \dfrac{1}{n}\),而 \(a_k\) 就是我们想求的值!
考虑 \(c_k\) 是什么,由 <3>,我们知道 \(c_k\) 是对于 \(f(x) = \sum\limits_{i = 0}^{n - 1}t_i \times x ^ i\) 这个多项式,代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 计算 \(f(x)\)(也就是 \(t\) 数组)的点值表示法。因此,我们只需要对 \(t_0, \cdots, t_{n - 1}\) 求出来点值表示即可,这个和上面 \(\mathbf{Part. 1 \ \ FFT}\) 唯一的不同就是代入值中单位根的次数一个为正,一个为负。
所以,\(\mathbf{IFFT}\) 实际上就是对 \(t\) 数组再做一遍 \(\mathbf{FFT}\),然后对于每个 \(c_k\) 乘上 \(\dfrac{1}{n}\) 就是 \(a_k\)。
\(\mathbf{Part. +\infin \ \ Details}\)
/*******************************
| Author: DE_aemmprty
| Problem: P3803 【模板】多项式乘法(FFT)
| Contest: Luogu
| URL: https://www.luogu.com.cn/problem/P3803
| When: 2025-07-26 15:03:20
|
| Memory: 500 MB
| Time: 2000 ms
*******************************/
#include <bits/stdc++.h>
using namespace std;
int read() {
char c = getchar(); int x = 0;
while ((c < '0' || c > '9') && c != '-') c = getchar();
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x;
}
const int N = 3e6 + 107;
const long double pi = acos(-1);
int n, m, id[N], l;
struct complexor {
double x, y;
complexor (double p = 0, double q = 0) : x(p), y(q) {}
complexor operator + (const complexor &t) const { return complexor(x + t.x, y + t.y); }
complexor operator - (const complexor &t) const { return complexor(x - t.x, y - t.y); }
complexor operator * (const complexor &t) const {
return complexor(x * t.x - y * t.y, x * t.y + y * t.x);
}
} p[N];
complexor omega(int n, int k) { return complexor(cos(pi * 2.0 * k / n), sin(pi * 2.0 * k / n)); }
void FFT(complexor *a, int op) {
for (int i = 0; i < l; i ++)
if (i < id[i]) swap(a[i], a[id[i]]);
for (int d = 1; d < l; d <<= 1) {
complexor w = omega((d << 1), op), W = 1;
for (int k = 0; k < d; k ++) {
for (int i = 0; i < l; i += (d << 1)) {
complexor at = a[i + k], bt = W * a[i + d + k];
a[i + k] = at + bt;
a[i + d + k] = at - bt;
}
W = W * w;
}
}
}
void solve() {
n = read(), m = read();
for (int i = 0; i <= n; i ++) p[i].x = read();
for (int i = 0; i <= m; i ++) p[i].y = read();
l = 1; while (l <= n + m) l <<= 1;
for (int i = 0; i < l; i ++)
id[i] = (id[i >> 1] >> 1) | ((i & 1) * (l >> 1));
FFT(p, 1);
for (int i = 0; i < l; i ++)
p[i] = p[i] * p[i];
FFT(p, -1);
for (int i = 0; i < n + m + 1; i ++)
cout << (int) (p[i].y / l / 2.0 + 0.5) << ' ';
cout << '\n';
}
signed main() {
int t = 1;
// t = read();
while (t --) solve();
return 0;
}
\(\mathbf{Question. 1}\)
第 \(42, 43\) 行的
swap是什么意思?(为什么不是递归写法?)
在 \(\mathbf{Part.1 \ \ FFT}\) 中,我们对每个多项式,按照奇偶分治。但是,我们在 \(\mathbf{FFT}\) 的过程中,如果递归的去做,我们还要在计算的过程中将偶数项排在前面,奇数项排在后面,这样的常数我们是不能接受的。那能不能快速的找到一种排序方式,直接把最终的顺序找出来,这样我能不用一边递归一边排序,而是一开始就排好序?
观察结构,你发现题目对于每个 \(i\),首先按照二进制第一位排序,其次按照二进制第二位排序……实际上,\(\mathbf{FFT}\) 的过程是在对每个 \(i\),它的二进制从低位到高位进行排序,也就是按照二进制的翻转串排序。(注意,这里的翻转指第 \(1\) 位翻转到第 \(n\) 位,而不是 \(0\to1,1\to0\))
所以第 \(i\) 个数的排名其实就是 \(i\) 的二进制翻转串的值。(如假设总长 \(l = 8\),\(i = 3 = (011)_2\),那么 \(i\) 的翻转串就是 \((011)_2 \to (110)_2\)。又由于最终序列按照翻转串来排序,所以 \(i\) 的排名就是 \((110)_2 = 6\))
那如何 \(\mathcal{O}(n)\) 求出来第 \(i\) 个数的翻转串的值用于求排名?
我们考虑递推。设 \(f_i\) 表示第 \(i\) 个数的翻转串的值,则我们考虑 比较 \(i\) 与 \(i' = i \gg 1\) 的关系。假设 \(i = (011)_2,i' = (001)_2\),那我们考虑两个数的翻转串分别为 \((110)_2, (100)_2\)。容易发现,由于 \(i\) 是 \(i’\) 左移一位加上 \((i \,\&\, 1)\),所以 \(i\) 的翻转串是 \(i'\) 右移一位加上 \((i\,\&\,1)\) 乘上最高位的值。因此,\(f_x = (f_{x \gg 1} \gg 1) \, \big| \, \Big((x \, \& \, 1) \times mx\Big)\),其中 \(mx\) 为最高位。
\(\mathbf{Question. 2}\)
为什么
swap之后,\(\mathbf{FFT}\) 求出来的值顺序不会乱呢?
考虑回顾 \(\mathbf{Part.1 \ \ FFT}\) 的内容。我们对于奇偶的下标,分别求出了 \(A_1(w_{\frac{n}{2}}^{i})\) 和 \(A_2(w_{\frac{n}{2}}^i)\) 的值,然后我们按顺序求出了 \(A(w_n^i)\) 的值并继续往上递归。我们这里要注意不能混淆:我们确实打乱了 \(A\) 多项式的系数表示的顺序,但是我们求出来的是点值表示,这个点值表示还是按照单位根的顺序求出来的。所以 swap 之后 \(A\) 多项式存的点值表示是按顺序的,没有影响。
FFT 学习笔记的更多相关文章
- 快速傅里叶变换(FFT)学习笔记
定义 多项式 系数表示法 设\(A(x)\)表示一个\(n-1\)次多项式,则所有项的系数组成的\(n\)维向量\((a_0,a_1,a_2,\dots,a_{n-1})\)唯一确定了这个多项式. 即 ...
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- 快速傅里叶变换(FFT)学习笔记(其一)
再探快速傅里叶变换(FFT)学习笔记(其一) 目录 再探快速傅里叶变换(FFT)学习笔记(其一) 写在前面 为什么写这篇博客 一些约定 前置知识 多项式卷积 多项式的系数表达式和点值表达式 单位根及其 ...
- 快速傅里叶变换(FFT)学习笔记(其二)(NTT)
再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...
- 口胡FFT现场(没准就听懂了)&&FFT学习笔记
前言(不想听的可以跳到下面) OK.蒟蒻又来口胡了. 自从ZJOI2019上Day的数论课上的多项式听到懵逼了,所以我就下定决心要学好多项式.感觉自己以前学的多项式都是假的. 但是一直在咕咕,现在是中 ...
- 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w
现在真是一碰电脑就很颓废啊... 于是早晨把电脑锁上然后在旁边啃了一节课多的算导, 把FFT的基本原理整明白了.. 但是我并不觉得自己能讲明白... Fast Fourier Transformati ...
- FFT学习笔记
快速傅里叶变换FFT(Fast Fourior Transform) 先说一下它能干嘛qwq 傅里叶变换有两种,连续傅里叶变换和离散傅里叶变换,OI中主要用来快速计算多项式卷积. 等一下,卷积是啥 ...
- 多项式乘法(FFT)学习笔记
------------------------------------------本文只探讨多项式乘法(FFT)在信息学中的应用如有错误或不明欢迎指出或提问,在此不胜感激 多项式 1.系数表示法 ...
- 【文文殿下】快速傅里叶变换(FFT)学习笔记
多项式 定义 形如\(A(x)=\sum_{i=0}^{n-1} a_i x^i\)的式子称为多项式. 我们把\(n\)称为该多项式的次数界. 显然,一个\(n-1\)次多项式的次数界为\(n\). ...
- 分治 FFT学习笔记
先给一道luogu板子题:P4721 [模板]分治 FFT 今天模拟有道题的部分分做法是分治fft,于是就学了一下.感觉不是很难,国赛上如果推出式子的话应该能写出来. 分治fft用来解决这么一个式子\ ...
随机推荐
- 完整版QQ(腾讯)开放平台操作指南(包含:qq登录能力获取等等)
之前我和大家提过,我要购买第三方的APP服务,就相当于有自己的APP了,现在APP对接上线之前需要做大量的准备工作,在此把步骤分享给大家,这样可以节省大家很多时间. 完整版QQ(腾讯)开放平台操作指南 ...
- Nacos源码—8.Nacos升级gRPC分析三
大纲 7.服务端对服务实例进行健康检查 8.服务下线如何注销注册表和客户端等信息 9.事件驱动架构源码分析 7.服务端对服务实例进行健康检查 (1)服务端对服务实例进行健康检查的设计逻辑 (2)服务端 ...
- .NET外挂系列:7. harmony在高级调试中的一些实战案例
一:背景 1. 讲故事 如果你读完前六篇,我相信你对 harmony 的简单使用应该是没什么问题了,现在你处于手拿锤子看谁都是钉子的情况,那这篇我就找高级调试里非常经典的 3个钉子 让大家捶一锤. 二 ...
- Linux日志 查找关键字及其前后的信息
文章<五分钟扫盲:25个工作中常用的Linux命令>介绍了常用的Linux命令,属于理论知识,这里知行合一,介绍如何从Linux日志中通过关键字过滤出我们需要的信息. 这里以查看名 ...
- 3D Gaussian splatting 03: 用户数据训练和结果查看
目录 3D Gaussian splatting 01: 环境搭建 3D Gaussian splatting 02: 快速评估 3D Gaussian splatting 03: 用户数据训练和结果 ...
- IntelliJ IDEA 2023.1 破解教程mac,windows,linux均适用/JetBrains产品全版本激活
前言 该激活方式不限于IDEA,同样也适用于JetBrains 全家桶的所有工具, 包括 IntelliJ IDEA.Pycharm.WebStorm.PhpStorm.AppCode.Datagri ...
- NSMutableDictionary 的内存布局
有关NSDictionary的内存布局,可以参看<NSDictionary 的内存布局>. 1 类图 和<NSDictionary 的内存布局>中的类图相比较,本章类图多了2个 ...
- .Net Web API 001 新建Net Web API工程
1.新建工程 打开VS2022,点击新建项目,弹出创建新项目对话框,然后在项目模板处,选择C#.所有平台以及WebAPI,如下图所示. 选择了下面的唯一模板,点击下一步,设置项目的名称.保存路径等.如 ...
- JavaScript中如何遍历对象?
JavaScript中如何遍历对象? 今天来点稍微轻松的话题,如何在JavaScript中遍历对象,在平常的工作中,遍历对象是很常见的操作,javascript提供了多种方法来遍历对象的属性.这些方法 ...
- C# WinForm 选择文件夹对话框
选择文件夹只有选择 FolderBrowserDialog,这种选择对话框让人有些抓狂,特别当文件目录比较深.需要多次选择文件夹操作时. 参考:自定义文件夹选择对话框 - 我也是个傻瓜 - 博客园 ( ...