多项式全家桶

多项式合集

前置知识:多项式的定义,表示方法,FFT,NTT,微积分等。

注意事项

  1. 多项式的封装很重要,现在一般都是用将指针传入一个函数的方式来进行多项式操作,如:void Inv(ll *f,ll *g,int n),表示对 n 次多项式 f 求逆,结果存在 g 中。
  2. 多项式数组多了一定要记得清空!!说的直接点,不要觉得你的空间很宝贵,现在一般 105 的数据范围能够你开 100 个数组!所以如果两个函数可能会共用一个数组的时候一定不要冒风险,该多开数组就开。

多项式乘法

模板

所有多项式操作的基础,这里提供一篇小学生也能看懂的博客,真的特别详细!从复数到单位根到优化全部讲了一遍,这里仅提供一下代码:

1. FFT

struct node
{
    double x,y;
    friend node operator +(node a,node b){return {a.x+b.x,a.y+b.y};}
    friend node operator -(node a,node b){return {a.x-b.x,a.y-b.y};}
    friend node operator *(node a,node b){return {a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
}f[N],g[N];
void FFT(node *f,int type)
{
    for(int i = 0;i < lim;i++)if(i < tr[i])swap(f[i],f[tr[i]]);
    for(int l = 2;l <= lim;l <<= 1)
    {
        node wn = {cos(2*Pi/l),sin(2*Pi/l)*type};int now = l>>1;
        for(int s = 0;s < lim;s += l)
        {
            node w = {1,0};int up = s|now;
            for(int i = s;i < up;i++,w = w*wn)
            {node mul = w*f[i|now];f[i|now] = f[i]-mul;f[i] = f[i]+mul;}
        }
    }
    if(~type)return ;
    for(int i = 0;i < lim;i++)
        f[i].x = f[i].x/lim+0.5,f[i].y = f[i].y/lim+0.5;
}
int main()
{
	n = rd();m = rd();
    for(int i = 0;i <= n;i++)f[i].x = rd();
    for(int i = 0;i <= m;i++)g[i].x = rd();
    while(lim <= n+m)lim <<= 1;
    for(int i = 0;i < lim;i++)tr[i] = tr[i>>1]>>1|((i&1)?lim>>1:0);
    FFT(f,1);FFT(g,1);
    for(int i = 0;i < lim;i++)f[i] = f[i]*g[i];
    FFT(f,-1);
    for(int i = 0;i <= n+m;i++)printf("%d ",(int)f[i].x);
    return 0;
}

2. NTT

FFT 的计算要用到小数,而小数不可避免会有精度问题,而且大多数情况下运算都是在模意义下进行的,所以就有了 NTT

NTT 事实上就是将 FFT 的单位根换成了原根,其他其实是完全一样的。

一般来说 NTT 的模数都是 998244353,因为 998244353=119×223+1,而一般情况下 n 肯定不会达到 223。如果模数不为 998244353,那么可以考虑用三个模数分别做再 CRT 合并,详见P4245【模板】任意模数多项式乘法

这里也只是给出 NTT 模板的代码:

ll qp(ll x,int y)
{
    ll ans = 1;
    for(;y;y >>= 1,x = x*x%mod)
        if(y&1)(ans *= x) %= mod;
    return ans;
}
int G = 3,invG = qp(3,mod-2),inv2 = qp(2,mod-2);
int jia(int x,int y){return x+y >= mod?x+y-mod:x+y;}
int jian(int x,int y){return x < y?x-y+mod:x-y;}
void NTT(ll *f,int type)
{
    for(int i = 0;i < lim;i++)if(i < tr[i])swap(f[i],f[tr[i]]);
    for(int l = 2;l <= lim;l <<= 1)
    {
        ll wn = qp(~type?G:invG,(mod-1)/l);int now = l>>1;
        for(int s = 0;s < lim;s += l)
        {
            ll w = 1;
            for(int i = s;i < s+now;i++,w = w*wn%mod)
            {int mul = w*f[i+now]%mod;f[i+now] = jian(f[i],mul);f[i] = jia(f[i],mul);}
        }
    }
}
int main()
{
	n = rd();m = rd();
    for(int i = 0;i <= n;i++)f[i] = rd();
    for(int i = 0;i <= m;i++)g[i] = rd();
    while(lim <= n+m)lim <<= 1;
    for(int i = 0;i < lim;i++)tr[i] = tr[i>>1]>>1|((i&1)?lim>>1:0);
    NTT(f,1);NTT(g,1);
    for(int i = 0;i <= lim;i++)f[i] = f[i]*g[i]%mod;
    NTT(f,-1);int invn = qp(lim,mod-2);
    for(int i = 0;i <= n+m;i++)printf("%lld ",f[i]*invn%mod);
    return 0;
}

在正式进入多项式全家桶前,还要介绍一些用于简化操作的函数:

  1. void cpy(ll *al,ll *ar,ll *b):表示将alar的数复制到b中。
void cpy(ll *al,ll *ar,ll *b){for(int i = 0;al != ar;al++,i++)*(b+i) = *al;}
  1. void clr(ll *al,ll *ar):表示将alar的数全部赋值为 0
void clr(ll *al,ll *ar){for(;al != ar;al++)*al = 0;}
  1. void pri(ll *f,int n),表示输出 n 次多项式 f
void pri(ll *f,int n)
{
    for(int i = 0;i <= n;i++)
        printf("%lld%c",f[i],i==n?'\n':' ');
}

并且,我们还要对多项试卷积进行封装:

void init(n):表示初始化 n 次多项式的蝴蝶变换数组。

void init(int n)
{
    lim = 1;while(lim <= n)lim <<= 1;
    for(int i = 0;i < lim;i++)tr[i] = tr[i>>1]>>1|(i&1?lim>>1:0);
}

void times(ll *f,ll *g,ll *a,int n,int m):表示求 fg 的卷积,结果存在 a 中。

void times(ll *f,ll *g,ll *a,int n,int m)
{
    cpy(f,f+n+1,a);cpy(g,g+m+1,b);init(n+m);
    clr(a+n+1,a+lim);clr(b+m+1,b+lim);
    NTT(a,1);NTT(b,1);
    for(int i = 0;i <= lim;i++)a[i] = a[i]*b[i]%mod;
    NTT(a,-1);
}

这里先将 fg 复制到了另一个数组中再进行的卷积,并且会卷积前会将不用的东西清空——以后实现的每个函数都应该这样:传进去的参数不应该会发生改变,也不用提前清空不必要的东西。

讲了这么多,终于要开始多项式全家桶了(


多项式求逆

P4238【模板】多项式乘法逆

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 F(x)×G(x)1(modxn)


如果只有一项,显然这一项就是 F0 的逆元,于是可以递归分治求解。

现在假设已经求出了 F(x)G0(x)1(modxn2)

又因为 F(x)G(x)1(modxn2),所以两式相减得:F(x)(G(x)G0(x))0(modxn2)

所以 G(x)G0(x)0(modxn2)

两边同时平方,由于 G(x)G0(x) 在模 xn2 意义下等于 0,所以 (G(x)G0(x))2 在模 xn 意义下就是 0

所以 G2(x)+G02(x)2G(x)G0(x)0(modxn)

两边同时乘 F(x),因为 F(x)G(x)1(modxn),所以 G(x)+F(x)G02(x)2G0(x)0(modxn)

所以 G(x)G0(x)(2F(x)G0(x))(modxn),然后就可以递归做了。

void Inv(ll *f,ll *g,int n)//G = G0(2-FG0)
{
    if(!n)return (void)(g[0] = qp(f[0],mod-2));
    Inv(f,g,n>>1);init(n << 1);
    cpy(f,f+n+1,a);clr(a+n+1,a+lim);clr(g+n+1,g+lim);
    NTT(a,1);NTT(g,1);
    for(int i = 0;i < lim;i++)
        g[i] = g[i]*(2-a[i]*g[i]%mod+mod)%mod;
    NTT(g,-1);clr(g+n+1,g+lim);
}

分治 FFT

P4721 【模板】分治 FFT

给定序列 g1n1,求序列 f0n1

其中 fi=j=1ifijgj,边界为 f0=1


这题也可以用多项式求逆来做:F(x)=11G(x)具体证明看题解吧

下面是分治的做法:

假设我们已经求出了 G[l,mid] 这一区间的数,现在我们要求 G[l,mid]G[mid+1,r] 的贡献。

根据定义可以发现:G[l,mid]G[mid+1,r] 的贡献事实上就是 G[l,mid]×F[0,rl],只需要做一遍卷积,然后再加到对应位置上即可。

为什么是 F[0,rl] 而不是 F[0,rmid]?因为你要考虑所有 G[l,mid]G[mid+1,r] 的贡献。就比如 GlFrl 就对 Gr 有贡献,所以最大应该取到 Frl 而不是 Frmid

void CDQ(ll *f,ll *g,int l,int r)//Inverse:F = 1/(1-G)(mod x^n)
{
    if(l == r)return ;
    int mid = l+r>>1;CDQ(f,g,l,mid);
    times(f,g+l,a,r-l,mid-l);
    for(int i = mid+1;i <= r;i++)g[i] = jia(g[i],a[i-l]);
    CDQ(f,g,mid+1,r);
}

多项式除法

P4512 【模板】多项式除法

给定一个 n 次多项式 F(x) 和一个 m 次多项式 G(x) ,求多项式 Q(x)R(x),满足以下条件:

  • Q(x) 次数为 nmR(x) 次数小于 m
  • F(x)=Q(x)G(x)+R(x)

F 是一个 n 次多项式,另 Fr(x) 表示将 F(x)0n 次项的系数翻转一遍后的多项式,可以发现其实 Fr(x)=xnF(1x)

F(x)=Q(x)×G(x)+R(x)F(1x)=Q(1x)×G(1x)+R(1x)xnF(1x)=xnmQ(1x)×xmG(1x)+xnm+1×xm1R(1x)Fr(x)=Qr(x)×Gr(x)+xnm1×Rr(x)

因为 Qrnm 次的,所以如果要求出 Q,我们完全可以在模 xnm+1 意义下进行求解,而刚好 Rr(x) 乘了个 xnm+1,所以可以直接丢掉。

那么 Fr(x)Qr(x)×Gr(x)(modxnm+1),所以 Qr(x)Fr(x)Gr(x)(modxnm+1)

于是可以算出 Q(x),然后 R(x)=F(x)Q(x)×G(x),就可以求出 R(x) 了。

void rvs(ll *f,ll *g,int n)//求F_r,存到G里面,F和G有可能是一个东西
{for(int i = 0;i <= n/2;i++){ll t = f[i];g[i] = f[n-i];g[n-i] = t;}}
ll inv_rvsg[N];
void div(ll *f,ll *g,ll *q,ll *r,int n,int m)//F = Q*G+R,Qr = Fr/Gr
{
    rvs(f,q,n);rvs(g,g,m);Inv(g,inv_rvsg,n-m);
    times(q,inv_rvsg,q,n,n-m);clr(q+n-m+1,q+lim);
    rvs(q,q,n-m);rvs(g,g,m);times(g,q,a,m,n-m);
    for(int i = 0;i <= m-1;i++)r[i] = jian(f[i],a[i]);
}

多项式开根

P5205 【模板】多项式开根

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G2(x)F(x)(modxn)。保证 F0=1


多项式开根和求逆其实差不多。

如果只有一项,显然这一项就是 1,也是考虑递归分治求解。

现在假设已经求出了 G02(x)F(x)(modxn2),因为 G2(x)F(x)(modxn2),所以:

G(x)G0(x)0(modxn2)(G(x)G0(x))20(modxn)G2(x)+G02(x)2G(x)G0(x)0(modxn)F(x)+G02(x)2G(x)G0(x)0(modxn)G(x)F(x)+G02(x)2G0(x)(modxn)

为了写代码方便,这里再继续化成:G(x)12(F(x)G0(x)+G0(x))。然后就可以做了。

ll invg[N];
void Sqrt(ll *f,ll *g,int n)//G = (F+G0^2)/2G0
{
    if(!n)return (void)(g[0] = 1);
    Sqrt(f,g,n>>1);
    Inv(g,invg,n);times(invg,f,invg,n,n);
    for(int i = 0;i <= n;i++)g[i] = (invg[i]+g[i])*inv2%mod;
    clr(g+n+1,g+lim);
}

加强版

P5277 【模板】多项式开根(加强版)

与模板唯一不同的是,F0 不一定是 1

那么我们只需要修改一下边界条件,相当于求一次 F0 的二次剩余。

由于 998244353 的原根是 3,那么 F0 一定可以表示为 3t,用 BSGS 求出即可。又因为题目保证了 F0 有二次剩余,所以 F0 的二次剩余就是 3t2

int BSGS(int a,int b)
{
    mp.clear();
    ll m = sqrt(mod)+1,now = b;
    for(int i = 0;i <= m;i++,now = now*a%mod)mp[now] = i;
    ll sum = qp(a,m);now = sum;
    for(int i = 1;i <= m;i++,now = now*sum%mod)
        if(mp.count(now))return i*m-mp[now];
    return -1;
}
int residue(int x,int k)//求x的k次剩余
{
    int ans = qp(G,BSGS(G,x)/k);
    return min(ans,mod-ans);
}
void Sqrt(ll *f,ll *g,int n)//G = (F+G0^2)/2G0
{
    if(!n)return (void)(g[0] = residue(f[0],2));
    ...
}

微积分

下面的部分就要涉及到微积分了,如果不知道的可以看这:什么是微积分?

一些求导法则:

  1. (xa)=axa1xadx=xa+1a+1
  2. (lnx)=1x(ex)=ex
  3. (f(x)±g(x))=f(x)±g(x)
  4. (f(x)×g(x))=f(x)g(x)+f(x)g(x)
  5. (f(g(x)))=f(g(x))×g(x)
  6. (f(x)g(x))=f(x)g(x)f(x)g(x)g2(x)

根据第一个求导法则,我们可以写出多项式的求导和积分函数:

void dao(ll *f,ll *g,int n)
{for(int i = 1;i <= n;i++)g[i-1] = f[i]*i%mod;g[n] = 0;}
void jifen(ll *f,ll *g,int n)
{for(int i = n;~i;i--)g[i+1] = f[i]*qp(i+1,mod-2)%mod;g[0] = 0;}

多项式对数函数

P4725 【模板】多项式对数函数(多项式 ln)

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G(x)lnF(x)(modxn)。保证 F0=1


G(x)lnF(x)(modxn) 两边同时求导得:G(x)F(x)F(x)(modxn)

所以 G(x)F(x)F(x)dx(modxn)

ll invf[N];
void ln(ll *f,ll *g,int n)//G=jifen(F'[i]/F[i])
{
    Inv(f,invf,n);dao(f,g,n);
    times(g,invf,g,n,n);clr(g+n+1,g+lim);
    jifen(g,g,n-1);
}

牛顿迭代

给定一个 n1 次多项式 F(x),已知有多项式 G(x) 满足 F(G(x))0(modxn),求 G(x)

先来看一个简单的问题:如何对一个很大的数 a 开方?很显然可以二分,但是二分每次都要算一个很大的数的平方,有没有什么更快的方法?

于是牛顿迭代产生了。

我们本质上是要求 f(x)=x2a0 的交点。假设我们已经求得了一个近似值 x0,牛顿迭代的做法即为过 (x0,f(x0)) 这个点做函数的切线,取切线与 x 轴的交点作为新的 x0

比如我们要求 50(图是盗的)

具体的,我们要求一条过 (x0,f(x0)),斜率为 f(x0) 的直线与 x 轴的交点。

很容易得出,切线方程为 y=f(x0)(xx0)+f(x0),当 y=0 时,x=x0f(x0)f(x0)

再回到原来的问题:求函数 G(x),使得 F(G(x))0(modxn)

假设我们已经求出了 F(G0(x))0,那么只需要每次令:

G(x)G0(x)F(G0(x))F(G0(x))

事实上,牛顿迭代每次都会让精度翻倍,即如果 F(G0(x))0(modxn2),那么有 F(G(x)0(modxn)。可以根据多项式求逆和开根感性理解。

多项式指数函数

P4726 【模板】多项式指数函数(多项式 exp)

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G(x)eF(x)(modxn)。保证 F0=0


因为 G(x)eF(x)(modxn),所以 lnG(x)F(x)0

根据牛顿迭代,我们可以令函数 f(G(x))=lnG(x)F(x)0,有 f(G0(x))=1G0(x)(注意一下这里不是复合函数求导)。

由牛顿迭代得:

(1)G(x)G0(x)f(G0(x))f(G0(x))(2)=G0(x)lnG0(x)F(x)1G0(x)(3)=G0(x)(1lnG0(x)+F(x))

然后就可以递归去做了。

ll lng[N];
void exp(ll *f,ll *g,int n)//G = G0(1-lnG0+F)
{
    if(!n)return (void)(g[0] = 1);
    exp(f,g,n>>1);ln(g,lng,n);
    for(int i = 0;i <= n;i++)lng[i] = jian(!i+f[i],lng[i]);
    times(g,lng,g,n,n);clr(g+n+1,g+lim);
}

再看多项式求逆和开方

在看到多项式求逆和开方的做法时,你可能会有点懵,为啥使用递归分治来做?为啥求了当前一半的答案就可以算出当前整个的答案?下面,我们再从牛顿迭代的角度推一遍。

多项式求逆

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 F(x)×G(x)1(modxn)

令函数 f(G(x))=1G(x)F(x)0,有 f(G(x))=1G2(x)

由牛顿迭代得:

(4)G(x)G0(x)f(G0(x))f(G0(x))(5)=G0(x)1G0(x)F(x)1G02(x)(6)=G0(x)(2F(x)G0(x))

是不是和之前推出来的式子完全一样?现在知道为啥要递归去做了吧!

多项式开方

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G2(x)F(x)(modxn)

令函数 f(G(x))=G2(x)F(x)0,有 f(G(x))=2G(x)

由牛顿迭代得:

(7)G(x)G0(x)f(G0(x))f(G0(x))(8)=G0(x)G02(x)F(x)2G0(x)(9)=G02(x)+F(x)2G0(x)

多项式快速幂

P5245 【模板】多项式快速幂

给定一个 n1 次多项式 F(x)k,求多项式 G(x) 使得 G(x)Fk(x)(modxn)。保证 F0=1


显然可以像普通快速幂一样直接做,但是这样子是 O(nlog2n) 的,有没有更快的方法?

考虑对 G(x)Fk(x) 两边同时取对数得 lnG(x)klnF(x)

然后再取指数得:G(x)eklnF(x),这样子就可以 O(nlogn) 做了。

void qpow(ll *f,ll *g,int n,int k)//G = e^{k*lnF}
{
    ln(f,lnf,n);
    for(int i = 0;i <= n;i++)lnf[i] = lnf[i]*k%mod;
    exp(lnf,g,n);
}

加强版

P5273 【模板】多项式幂函数(加强版)

给定一个 n1 次多项式 F(x)k,求多项式 G(x) 使得 G(x)Fk(x)(modxn)。不保证 F0=1


如果 F01,那么可以考虑先给多项式除以一个 F0,然后做快速幂,最后再乘上 F0k

但是还有一种可能——F0=0,这种情况下需要找到最低的系数不为 0 的那一项。

设这一项为 c×xd,那么让整个多项式除以这一项就能保证第一项为 1 了。做了快速幂之后再让整个多项式乘 ck×xdk 即可。

要注意的是 ck 应该对 φ(998244353) 取模,而不是对 998244353。还要注意判断 dk 是否比 n 大,所以在读入模数时应该记录三个值。

ll ff[N];
void exqpow(ll *f,ll *g,int n,char *s)//f = ff*(c*x^d)
{
    int len = strlen(s+1);
    ll k1 = 0,k2 = 0,k3 = 0;
    for(int i = 1;i <= len;i++)
    {
        k1 = (k1*10+(s[i]^48))%mod;k2 = (k2*10+(s[i]^48))%phimod;
        if(i <= 6)k3 = k3*10+(s[i]^48);
    }
    ll c = 0,d,invc;
    for(int i = 0;i <= n;i++)
    {
        if(!c&&f[i])invc = qp(c = f[i],mod-2),d = i;
        if(c)ff[i-d] = f[i]*invc%mod;
    }
    if(!c)return (void)clr(g,g+n+1);
    qpow(ff,g,n,k1);d = min(n+1ll,d*k3);c = qp(c,k2);
    for(int i = n;~i;i--)g[i] = i < d?0:g[i-d]*c%mod;
}

多项式三角函数

P5264 多项式三角函数

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G(x)sinF(x)(modxn)G(x)cosF(x)(modxn)。保证 F0=0


首先你需要知道大名鼎鼎的欧拉公式:eiθ=cosθ+isinθ

θ=xeix=cosx+isinx;取 θ=xeix=cosxisinx

两式相加再除以 2cosx=eix+eix2;两式相加再除以 2isinx=eixeix2i

还有个问题,i 在模意义下是多少?

因为 i2=1,所以 i2998244352(mod998244353),求一遍二次剩余即可。

ll I = residue(mod-1,2),invI = mod-I;
ll expg[N],inv_expg[N];
void sin(ll *f,ll *g,int n)//sinx = (e^ix-e^-ix)/2i
{
    for(int i = 0;i <= n;i++)g[i] = f[i]*I%mod;
    exp(g,expg,n);Inv(expg,inv_expg,n);
    int mul = inv2*invI%mod;
    for(int i = 0;i <= n;i++)g[i] = (expg[i]-inv_expg[i]+mod)*mul%mod;
}
void cos(ll *f,ll *g,int n)//cosx = (e^ix+e^-ix)/2
{
    for(int i = 0;i <= n;i++)g[i] = f[i]*I%mod;
    exp(g,expg,n);Inv(expg,inv_expg,n);
    for(int i = 0;i <= n;i++)g[i] = (expg[i]+inv_expg[i])*inv2%mod;
}

多项式反三角函数

P5265 多项式反三角函数

给定一个 n1 次多项式 F(x),求多项式 G(x) 使得 G(x)arcsinF(x)(modxn)G(x)arctanF(x)(modxn)。保证 F0=0

有两个式子:

arcsinx=ln(ix+1x2)iarctanx=ln(1+ix)ln(1ix)2i

证:

  1. arcsin

arcsinx=θx=sinθ=eiθeiθ2i

p=eiθ

x=p1p2ip22ixp1=0p=ix+1x2iθ=ln(ix+1x2)θ=ln(ix+1x2)i

  1. arctan

(10)arctanx=θx=sinθcosθ(11)=eiθeiθ2i×2eiθ+eiθ(12)=eiθeiθi(eiθ+eiθ)

p=eiθ

x=p1pi(p+1p)ix=p21p2+1(1ix)p2=1+ixp=1+ix1ix(13)iθ=ln1+ix1ix(14)=ln(1+ix)ln(1ix)2θ=ln(1+ix)ln(1ix)2i

证毕,套板子。

ll sqg[N];
void asin(ll *f,ll *g,int n)//asinx = ln(ix+sqrt(1-x^2))/i
{
    times(f,f,g,n,n);
    for(int i = 0;i <= n;i++)g[i] = jian(!i,g[i]);
    Sqrt(g,sqg,n);
    for(int i = 0;i <= n;i++)(sqg[i] += f[i]*I) %= mod;
    ln(sqg,g,n);
    for(int i = 0;i <= n;i++)(g[i] *= invI) %= mod;
}
ll lng1[N],lng2[N];
void atan(ll *f,ll *g,int n)//atanx = (ln(1+ix)-ln(1-ix))/2i
{
    for(int i = 0;i <= n;i++)g[i] = (!i+f[i]*I)%mod;
    ln(g,lng1,n);
    for(int i = 0;i <= n;i++)g[i] = jian(!i,f[i]*I%mod);
    ln(g,lng2,n);int mul = inv2*invI%mod;
    for(int i = 0;i <= n;i++)g[i] = (lng1[i]-lng2[i]+mod)*mul%mod;
}
posted @   max0810  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示