首页 > 其他分享 >FFT 学习笔记

FFT 学习笔记

时间:2024-07-02 19:21:28浏览次数:17  
标签:frac int text sum FFT 笔记 学习 多项式 2n

\(\text{FFT}\) 学习笔记

多项式

确定一个多项式,往往只需要知道每一次项前的系数是多少即可。众所周知,一个朴素的多项式往往可以被写成

\[f(x)=\sum_{n\ge 0}a_nx^n \]

的形式,在这种形式下的两个多项式 \(f,g\) 的乘积 \(h\) 往往可以按照

\[h(x)=(f*g)(x)=\sum_{n\ge 0}(\sum_{i=0}^{n}a_ib_{n-i})x^n \]

的方式计算。也就是说,得到的新多项式的每一次项前的系数可以写作

\[c_n=\sum_{i=0}^na_ib_{n-i} \]

这种形式被叫做卷积,其特征是两个相乘的数所对应的下标相加是一个定值。不难发现,如此计算,对于两个次数为 \(n\) 的多项式来说,复杂度是 \(O(n^2)\),然而这非常的慢,这个时候就需要利用 \(\text{FFT}\),即快速傅里叶变换进行求解。

考虑 \(\text{FFT}\) 是如何优化的。对于一个 \(n\) 次多项式来说,只需要给定 \(n+1\) 个点,便可以确定一个唯一的 \(n\) 次多项式。那么我们在两个 \(n\) 次函数上取横坐标相同的 \(n+1\) 个点 \((x_i,y_i)\),那么对于新函数来说,新得到的 \(n+1\) 个点的坐标一定为 \((x_i,{y_1}_i{y_2}_i)\)。不难发现,此时,我们只需要 \(O(n)\) 的时间复杂度即可确定新得到的多项式。那么,现在需要做得就是对于多项式的系数表示和点值表示进行一个快速的转换。

复数

即形如 \(a+bi\) 形式的数,其中 \(a,b\) 为实数,\(i\) 为虚数单位,即 \(\sqrt{-1}\)。

接着引入复平面,也就是以实数为 \(x\) 轴,虚数为 \(y\) 轴建立的平面直角坐标系,一个复数都在复平面上对应一个点。

复数的加减只需要将实数部分和虚数部分相加减即可。对于乘法则有

\[(a+bi)(c+di)=(ac-bd)+(ad+bc)i \]

单位根

对于一个 \(n\) 次方程,一般来说都有 \(n\) 个根,而对于方程

\[x^n=1 \]

的这 \(n\) 个根,对应到复平面上来看,我们以原点为圆心,做一个半径为 \(1\) 的圆,称为单位圆,并将其 \(n\) 等分,与 \(x\) 正半轴相交的点为一个根(即 \(1\))。接着从 \(x\) 轴出发逆时针旋转到第 \(1\) 个点,这个点即为单位根,记作 \(w_n\)。接下来将第 \(i\) 个点记作 \(w_n^i\),不难发现有以下性质:

  1. \(w_{n}^k=w_{n}^{n+k}\)
  2. \(w_n^k=w_{2n}^{2k}\)
  3. \(w_{2n}^{n+k}=-w_{2n}^{k}\)
  4. \((w_n^k)^a=w_n^{ak}\)
  5. \(w_n^k\) 对于 \(k\in[0,n-1]\) 互不相等

对于单位根的计算,可以利用三角函数简单求解,即

\[w_n=\text{cos}(\frac{2\pi}{n})+\text{sin}(\frac{2\pi}{n})i \]

离散傅里叶变换

即 \(\text{DFT}\),是一种将系数表示快速转换成点值表示的变换。

对于一个 \(2n-1\) 次多项式 \(f(x)\):

\[f(x)=a_0+a_1x+a_2x^2+\cdots+a_{2n-1}x^{2n-1} \]

如果我们分别设

\[g(x)=a_0+a_2x+a_4x^2+\cdots\\h(x)=a_1+a_3x+a_5x^2+\cdots \]

那么显然有 \(f(x)=g(x^2)+xh(x^2)\),不妨代入 \(x=w_{2n}^k\),那么有

\[\begin{aligned}f(w_{2n}^k)&=g((w_{2n}^k)^2)+w_{2n}^kh((w_{2n}^k)^2)\\&=g(w_{2n}^{2k})+w_{2n}^{k}h(w_{2n}^{2k})\\&=g(w_n^k)+w_{2n}^{k}h(w_n^k) \end{aligned} \]

同理有

\[\begin{aligned}f(w_{2n}^{n+k})&=g((w_{2n}^{n+k})^2)+w_{2n}^{n+k}h((w_{2n}^{n+k})^2)\\&=g(w_{2n}^{2n+2k})-w_{2n}^{k}h(w_{2n}^{2n+2k})\\&=g(w_{2n}^{2k})-w_{2n}^{k}h(w_{2n}^{2k})\\&=g(w_n^k)-w_{2n}^{k}h(w_n^k) \end{aligned} \]

因此当我们求出 \(g(w_n^k)\) 和 \(h(w_n^k)\) 后便可以得到 \(f(w_{2n}^k)\) 和 \(f(w_{2n}^{n+k})\)。

由此我们考虑对于多项式 \(f\) 的 \(2n\) 个点分别代入 \(x=w_{2n}^i\),那么每一个值均可以通过交换系数后求解 \(g,h\),接着通过求解出的 \(g,h\) 更新所求的 \(f\)。为了让合并容易进行,不妨将次数补齐到 \(2^k-1\) 次。复杂度是 \(O(n\log n)\) 的。

但是这样写常数很大,我们考虑模拟交换系数的过程:

\[0,1,2,3,4,5,6,7\\ 0,2,4,6,1,3,5,7\\ 0,4,2,6,1,5,3,7 \]

我们考虑这些数的二进制表示:

\[000,001,010,011,100,101,110,111\\ 000,010,100,110,001,011,101,111\\ 000,100,010,110,001,101,011,111 \]

不难发现最初系数的位置和最终系数的位置满足下标进行了一次二进制下的翻转。

于是我们不难联想到与处理出位置是如何变化的,并存储在 \(r\) 数组中,接着考虑合并时本质上是将 \(2n\) 个位置中的 \(k,n+k\) 合并至 \(k,n+k\),因此可以直接进行覆盖。这样我们就完成了常数上的优化。

代码

void DFT(int n){
    Set(n-1);
    for(int i=0;i<n;i++){
        if(i<r[i])swap(a[i],a[r[i]]);//按照预处理交换系数
    }
    for(int len=1;len<n;len<<=1){
        Complex step(cos(Pi/len),sin(Pi/len));//单位根
        for(int l=0;l<n;l+=(len<<1)){
            Complex w(1,0);
            for(int k=0;k<len;k++,w=w*step){
                Complex A=a[l+k],B=w*a[l+len+k];
                a[l+k]=A+B;
                a[l+len+k]=A-B;
            }
        }
    }
    return ;
}

逆离散傅里叶变换

即 \(\text{IDFT}\),也就是将函数从点值表示转换成系数表示的变换。

考虑 \(\text{DFT}\) 在线性代数上的表示,即为

\[\begin{bmatrix}y_0\\y_1\\y_2\\\vdots\\y_{2n-1}\end{bmatrix}=\begin{bmatrix}w_{2n}^0&w_{2n}^0&w_{2n}^0&\cdots&w_{2n}^{0}\\w_{2n}^0&w_{2n}^1&w_{2n}^2&\cdots&w_{2n}^{n-1}\\w_{2n}^0&w_{2n}^2&w_{2n}^{4}&\cdots&w_{2n}^{2(n-1)}\\\vdots&\vdots&\vdots&\ddots&\vdots\\w_{2n}^0&w_{2n}^{n-1}&w_{2n}^{2(n-1)}&\cdots&w_{2n}^{(n-1)^2}\end{bmatrix}\begin{bmatrix}a_0\\a_1\\a_2\\\vdots\\a_{2n-1}\end{bmatrix} \]

记点值矩阵为 \(E\),中间的矩阵为 \(D\),系数矩阵为 \(V\),则 \(E=DV\),那么有 \(V=ED^{-1}\),也就是我们只需要求出 \(D\) 矩阵的逆矩阵即可。

考虑构造 \(A_{ij}=D_{ij}^{-1}\),计算 \(AD\):

\[\begin{aligned}(AD)_{ij}&=\sum_{k=0}^{2n-1}A_{ik}D_{kj}\\&=\sum_{k=0}^{2n-1}w_{2n}^{-ik}w_{2n}^{kj}\\&=\sum_{k=0}^{2n-1}w_{2n}^{k(j-i)}\\&= \left\{\begin{array}{l}n&(i=j)\\\sum_{k=0}^{2n-1}(w_{2n}^{j-i})^k=\frac{(w_{2n}^{j-i})^{2n}-1}{w_{2n}^{j-i}-1}=\frac{(w_{2n}^{2n})^{j-i}-1}{w_{2n}^{j-i}-1}=0&(i\ne j)\end{array}\right.\end{aligned} \]

不难发现 \(\frac{AD}{n}=I\),所以有 \(D^{-1}=\frac{A}{n}\)。更具体的,因为 \(A_{ij}=D_{ij}^{-1}\),所以每个位置代入的数就变成了 \(w_{2n}^{-i}\),与 \(w_{2n}^{i}\) 关于 \(x\) 轴对称,也就是虚数部分相反。只需要对于 \(\text{DFT}\) 函数传于一个参数 \(\text{type}\) 表示如何转换即可。

代码

void DFT(int n,int type){
    Set(n-1);
    for(int i=0;i<n;i++){
        if(i<r[i])swap(a[i],a[r[i]]);
    }
    for(int len=1;len<n;len<<=1){
        Complex step(cos(Pi/len),type*sin(Pi/len));//是否需要取反
        for(int l=0;l<n;l+=(len<<1)){
            Complex w(1,0);
            for(int k=0;k<len;k++,w=w*step){
                Complex A=a[l+k],B=w*a[l+len+k];
                a[l+k]=A+B;
                a[l+len+k]=A-B;
            }
        }
    }
    if(type==-1){
        for(int i=0;i<n;i++)a[i].a/=n;//除以 n 才是正确结果
    }
    return ;
}

下面可以给出一个带封装的总代码:

const int N=2097152;//注意需要取到最大次数向上的最大二次幂数
const double Pi=acos(-1.0);//处理 2pi

int n,m;
int r[N];//预处理数组
struct Complex{
    double a,b;
    Complex(double a=0,double b=0):a(a),b(b){}
    Complex operator +(Complex x){return {a+x.a,b+x.b};}
    Complex operator -(Complex x){return {a-x.a,b-x.b};}
    Complex operator *(Complex x){return {a*x.a-b*x.b,a*x.b+b*x.a};}
};//手写复数
struct Polynomial{
    int len;
    vector<Complex>a;
    Complex& operator [](int x){return a[x];}
    void Set(int len){this->len=len,a.resize(len+1);}//节省空间的做法
    void DFT(int n,int type){
        Set(n-1);
        for(int i=0;i<n;i++){
            if(i<r[i])swap(a[i],a[r[i]]);
        }
        for(int len=1;len<n;len<<=1){
            Complex step(cos(Pi/len),type*sin(Pi/len));
            for(int l=0;l<n;l+=(len<<1)){
                Complex w(1,0);
                for(int k=0;k<len;k++,w=w*step){
                    Complex A=a[l+k],B=w*a[l+len+k];
                    a[l+k]=A+B;
                    a[l+len+k]=A-B;
                }
            }
        }
        if(type==-1){
            for(int i=0;i<n;i++)a[i].a/=n;
        }
        return ;
    }
    Polynomial operator *(Polynomial x){
        Polynomial y=*this,z;
        int len=x.len+y.len;
        int n=1;
        while(n<=len)n<<=1;//向上取到二次幂
        z.Set(n-1);
        for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
        x.DFT(n,1);
        y.DFT(n,1);
        for(int i=0;i<n;i++)z[i]=x[i]*y[i];
        z.DFT(n,-1);
        z.Set(len);
        return z;
    }
};
Polynomial f,g;

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    f.Set(n);
    g.Set(m);
    for(int i=0;i<=n;i++)cin>>f[i].a;
    for(int i=0;i<=m;i++)cin>>g[i].a;
    f=f*g;
    for(int i=0;i<=f.len;i++)cout<<(int)(f[i].a+0.5)<<" ";//精度问题
    return 0;
}

快速数论变换

即 \(\text{NTT}\),考虑到 \(\text{FFT}\) 存在精度问题,对于模意义下的数就需要用到 \(\text{NTT}\) 了。

考虑到此时单位根不再使用,我们考虑模意义下能够代替单位根的东西,也就是上面讲到的原根。考虑原根如何能够满足代替单位根进行运算,考虑一些性质。

  1. \(w_n^k\) 对于 \(k\in[0,n-1]\) 互不相等。

    若模数 \(p\) 是一个质数,那么对于原根 \(g\),\(g^k\) 对于 \(k\in[0,p-1]\) 在模 \(p\) 意义下互不相等,那么 \((g^{\frac{p-1}{n}})^k\) 对于 \(k\in[0,n-1]\) 在模 \(p\) 意义下互不相等。

  2. \(w_n^k=w_n^{n+k}\)。

    \((g^{\frac{p-1}{n}})^{n+k}=g^{p-1}(g^{\frac{p-1}{n}})^k\equiv(g^{\frac{p-1}{n}})^k\pmod p\)

  3. \(w_n^k=w_{2n}^{2k}\)。

    \((g^{\frac{p-1}{n}})^k=g^{\frac{k(p-1)}{n}}=g^{\frac{2k(p-1)}{2n}}=(g^{\frac{p-1}{2n}})^{2k}\pmod p\)

  4. \(w_{2n}^{n+k}=-w_{2n}^{k}\)。

    \(\frac{(g^{\frac{p-1}{2n}})^{n+k}}{(g^{\frac{p-1}{2n}})^k}=(g^{\frac{p-1}{2n}})^n=g^{\frac{p-1}{2}}\equiv -1\pmod p\),所以 \((g^{\frac{p-1}{2n}})^{n+k}\equiv-(g^{\frac{p-1}{2n}})^k\)。

  5. \((w_n^k)^a=w_n^{ak}\)。

    \(((g^{\frac{p-1}{n}})^k)^a=(g^{\frac{p-1}{n}})^{ak}\)。

因此,不难看出,单位根即为 \(g^{\frac{p-1}{n}}\),同时,这也对 \(p\) 作出了相应的限制,假设运算过程中多项式的次数最多到达 \(2^t\),那么 \(p=a2^\alpha+1,\alpha\ge t\)。下面是一些常见的 \(\text{NTT}\) 模数。

\[998244353=119\times 2^{23}+1,g=3\\ 469762049=7\times 2^{26}+1,g=3\\ 1004535809=479\times 2^{21}+1,g=3 \]

那么当题目给出的模数不是符合要求的 \(\text{NTT}\) 模数时应该怎么办呢——大骂出题人。

代码

#define ll long long
const int N=1e7+5,M=1e5+5,R=262144,P=1004535809,G=3,GI=334845270;

int n,m,t,s;
int w[M],r[R];
ll ans;
ll f[N],g[N];
namespace Math{
    ll QuickPow(ll a,int b,const int p){
        ll res=1;
        while(b>0){
            if((b&1)==1)res=res*a%p;
            a=a*a%p;
            b>>=1;
        }
        return res;
    }
    ll Inv(int x,int p){
        return QuickPow(x,p-2,p);
    }
};
struct Polynomial{
    int len;
    vector<ll>a;
    ll& operator [](int x){return a[x];}
    void Set(int len){this->len=len,a.resize(len+1);}
    void NTT(int n,int type){
        Set(n-1);
        for(int i=0;i<n;i++){
            if(i<r[i])swap(a[i],a[r[i]]);
        }
        for(int len=1;len<n;len<<=1){
            ll step=Math::QuickPow(type==1?G:GI,(P-1)/(len<<1),P);
            for(int l=0;l<n;l+=(len<<1)){
                ll w=1;
                for(int k=0;k<len;k++,w=w*step%P){
                    ll A=a[l+k],B=w*a[l+len+k]%P;
                    a[l+k]=(A+B)%P;
                    a[l+len+k]=(A-B)%P;
                }
            }
        }
        if(type==-1){
            ll inv=Math::Inv(n,P);
            for(int i=0;i<n;i++)a[i]=a[i]*inv%P;
        }
        return ;
    }
    Polynomial operator *(Polynomial x){
        Polynomial y=*this,z;
        int len=x.len+y.len;
        int n=1;
        while(n<=len)n<<=1;
        z.Set(n-1);
        for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
        x.NTT(n,1);
        y.NTT(n,1);
        for(int i=0;i<n;i++)z[i]=x[i]*y[i]%P;
        z.NTT(n,-1);
        z.Set(len);
        return z;
    }
}A,B;

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    A.Set(n);
    B.Set(m);
    for(int i=0;i<=n;i++)cin>>A[i];
    for(int i=0;i<=m;i++)cin>>B[i];
    A=A*B;
    for(int i=0;i<=A.len;i++)cout<<A[i]<<" ";
    return 0;
}

常见模型

事实上,所有的模型都离不开以下两种情况:

\[c_k=\sum_{i=0}^{k}a_ib_{k-i}\\ c_k=\sum_{i=0}^{k}a_ib_{k+i} \]

上面的就是朴素的卷积,而下面的称作差卷积,即两个数下标的差为定值。对于这种情况,我们通常选择讲一个序列倒转,即构造 \(b'_i=b_{n-i}\),那么有

\[c_k=\sum_{i=0}^{k}a_ib'_{n-(k+i)}=\sum_{i=0}^{k}a_ib'_{n-k-i} \]

于是发现和始终为 \(n-k\),可以求解。

字符串匹配

虽然很不想承认,但是 \(\text{FFT}\) 确实适用于字符串匹配的情景,考虑如何维护两个串是否相同,相同则意味着每一位都相同,因此我们构造

\[c_k=\sum_{i=0}^{m-1}(a_{k+i}-b_{i})^2 \]

不难发现,当 \(c_k=0\) 时,意味着从模式串的第 \(k\) 位开始能够成功匹配,那么进行一些化简:

\[c_k=\sum_{i=0}^{m-1}a_{k+i}^2-2\sum_{i=0}^{m-1}a_{k+i}b_{i}+\sum_{i=0}^{m-1}b_i^2 \]

很显然,除了中间那一项,其他的项均可以通过预处理 \(O(1)\) 得出,而中间显然是一个差卷积形式,因此构造 \(b'_{i}=b_{m-1-i}\),所以有

\[c_k=\sum_{i=0}^{m-1}a_{k+i}^2+b_i^2-2\sum_{i=0}^{m-1}a_{k+i}b'_{m-1-i} \]

\(\text{FFT}\) 求解即可,复杂度是 \(O(n\log n)\) 的。而对于存在通配符的匹配,我们认为通配符对应值为 \(0\),这意味着只要有通配符那么这一项就认为相等,因此构造

\[c_k=\sum_{i=0}^{m-1}a_{k+i}b_i(a_{k+i}-b{i})^2 \]

化简可得\

\[\begin{aligned}c_k&=\sum_{i=0}^{m-1}a_{k+i}^3b_i+ \sum_{i=0}^{m-1}a_{k+i}b_i^3-2\sum_{i=0}^{m-1}a_{k+i}^2b_i^2\\&=\sum_{i=0}^{m-1}a_{k+i}^3b_{m-1-i}+\sum_{i=0}^{m-1}a_{k+i}b_{m-1-i}^3-2\sum_{i=0}^{m-1}a_{k+i}^2b_{m-1-i}^2 \end{aligned} \]

不难看出只需要进行 \(3\) 次 \(\text{FFT}\) 即可。

背包问题

类似于求解给出 \(n\) 个数,选出 \(k\) 个数(可重复),求解和在某一范围内的方案数。不难想出构造 \(a_i\) 的值为有多少个值为 \(i\) 的数,所求即为询问范围内 \((a^k)_i\) 的和。需要注意,为了保证时间和空间的正确性,每次处理完后,对于值超过范围的数应当忽视,因为他们一定不符合要求。但是复杂度为 \(O(kn\log n)\) 依旧不优,考虑二进制优化,那么复杂度优化为 \(O(n\log n\log k)\)。

高精度乘法

据某不知名消息途径,\(\text{Python}\) 的高精度乘法就是用 \(\text{FFT}\) 实现的。

考虑一个十进制数的一种不常规写法

\[n=a_0+a_1\times10+a_2\times 10^2+\cdots \]

于是不妨假设 \(x=10\),那么

\[n=a_0+a_1x+a_2x^2+\cdots \]

这和多项式没有区别,于是两个数相乘就可以抽象成两个多项式相乘,因此可以利用 \(\text{FFT}\) 优化,复杂度从 \(O(n^2)\) 降至 \(O(n\log n)\)。

标签:frac,int,text,sum,FFT,笔记,学习,多项式,2n
From: https://www.cnblogs.com/DycBlog/p/18280405

相关文章

  • Python TensorFlow双向Bi-LSTM长短期记忆神经网络深度学习可视化用户传感器活动数据
    全文链接:https://tecdat.cn/?p=36613原文出处:拓端数据部落公众号在本文中,我们旨在利用深度学习技术,特别是TensorFlow框架下的Keras库,对WISDM(无线传感器数据挖掘)数据集进行活动识别。WISDM数据集包含了从用户身上佩戴的加速度传感器收集的三轴加速度数据,这些数据被用于识别用户的......
  • 焦点损失:深度学习中的目标检测优化神器
    ......
  • learncpp网站学习笔记
    0.1课程简介教程特点:零基础适用、示例丰富课程结构:第0章介绍c++编程的相关概念及软件;第1章介绍c++基础,后面章节深入研究;每章都有一个主题目标涵盖一般的编程主题:编程风格、常见陷阱、调试、好/坏的编程实践、测试提供大量示例(尽量不在示例中省略内容、引入未解释过的概念......
  • postman使用笔记
    Postman是一个广泛使用的API开发工具,它提供了一个用户友好的图形界面来发送HTTP请求、查看响应、组织测试用例和创建自动化测试。以下是一些基本的Postman使用教程,结合了搜索结果中的信息:安装Postman访问Postman官方网站下载适用于Windows、MacOS和Linux的......
  • odoo学习-2
    1.新加自定义模块odoo同级目录下新建my_addons文件夹加入自己的模块(注意:views中也要创建一个xml文件)  2.model代码-写在models下面的py文件中fromodooimportapi,fields,modelsclassEpidemicRecord(models.Model):_name='epidemic.record'#数据库......
  • A tour of C++ 读书笔记
    第一章:C++只是个编译型语言,需要源文件编译成目标文件,再通过链接各种库到可执行文件1.6常量  const  constexpr这个代表是要在编译的时候估值,性能会有所增加吧2.4联合体(union)  联合体所有的成员都是分配在同一地址上面,所以联合体所占的空间是跟其自身内部成员所......
  • 2024.07.02【读书笔记】|医疗科技创新流程(第二章 创新创造 监管基础概述)
    监管基础概述监管基础涉及对医疗设备监管环境的理解,包括监管机构的组织结构、监管途径以及产品分类等。美国食品药品监督管理局(FDA)是全球领先的监管机构,其对医疗设备的监管流程为全球许多其他国家的监管机构所借鉴。FDA的背景和组织结构FDA是一个科学性的监管机构,负责保......
  • 2024.07.02【读书笔记】|医疗科技创新流程(第二章 创新创造 报销基础概述)
    报销基础概述在医疗领域,报销是指医疗设备、药品或服务在提供给患者后,由保险公司或支付机构对其进行补偿的过程。报销基础是医疗设备创新过程中的一个重要组成部分,它影响着产品的市场成功和可及性。报销的重要性医疗设备的报销直接影响到产品的市场接受度和销售潜力。如果......
  • python学习-list
    List(列表的定义语法)[元素1,元素2,元素3,......]什么是元素?数据容器内的每一份数据,都称之为元素元素的类型有限制吗?元素的数据类型没有任何限制,甚至元素也可以是列表,这样就定义了嵌套列表但是打印列表里的数值类型是'list'列表的下标(索引)列表的下标(索引)-反向......
  • 学习笔记484—Word加载项是灰色怎么解决 Word加载项是灰色的解决方法【详解】
    Word加载项是灰色怎么解决?在Word2016拥有一个加载项的功能,加载项其实就是Word插件,可以实现很多Word自己无法实现的功能,近期有用户发现自己电脑上的Word加载项是灰色的无法使用,这该怎么解决呢?下面我们来看看吧。具体操作如下:1、首先我们打开Word。具体查看图片哦。我这个......