首页 > 其他分享 >多项式除法及其应用

多项式除法及其应用

时间:2023-01-24 20:34:37浏览次数:30  
标签:int 多项式 frac1x beta 应用 除法 我们 left

$part ~ 1 ~ $多项式除法

01 问题描述

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

  • \(Q(x)\) 次数为 \(n-m\),\(R(x)\) 次数小于 \(m\)
  • \(F(x) = Q(x) * G(x) + R(x)\)

所有的运算在模 \(998244353\) 意义下进行。

输入格式

第一行两个整数 \(n\),\(m\),意义如上。
第二行 \(n+1\) 个整数,从低到高表示 \(F(x)\) 的各个系数。
第三行 \(m+1\) 个整数,从低到高表示 \(G(x)\) 的各个系数。

输出格式

第一行 \(n-m+1\) 个整数,从低到高表示 \(Q(x)\) 的各个系数。
第二行 \(m\) 个整数,从低到高表示 \(R(x)\) 的各个系数。
如果 \(R(x)\) 不足 \(m-1\) 次,多余的项系数补 \(0\)。

样例 #1
样例输入 #1
5 1
1 9 2 6 0 8
1 7
样例输出 #1
237340659 335104102 649004347 448191342 855638018
760903695
提示

对于所有数据,\(1 \le m < n \le 10^5\),给出的系数均属于 \([0, 998244353) \cap \mathbb{Z}\)。

02 巧妙的转化

首先由题意,我们得到的是如下关系式:

\[F(x)=G(x)Q(x)+R(x) \]

先求一发对应的几个多项式的次数范围:

\(F(x)\)次数\(\left[0,n\right]\),共\(n+1\)项

\(G(x)\)次数\(\left[0,m\right]\),共\(m+1\)项

\(Q(x)\)次数\(\left[0,n-m\right]\),共\(n-m+1\)项

\(R(x)\)次数\(\left[0,m-1\right]\),共\(m\)项

做完多项式求逆后会很自然地想到求逆就是为了除法服务的,于是我们会发现如果没有\(R(x)\)余项的话很显然我们的除法会直接变为一道求逆题. 由于整个式子的多项式范围都在\(x^n\)范围内,如果等式是简单的\(F(x)=G(x)Q(x)\)的话,我们可以化为在\(\pmod{x^{n+1}}\)的意义下求\(G^{-1}(x)\),而对应的会有\(Q(x)\equiv F(x)Q^{-1}(x)\pmod{x^{n+1}}\),十分简单

而对于目前我们出现的\(R(x)\)增加的复杂性,我们考虑的思路便是先将\(R(x)\)给消去,这里一个很巧妙的思路便是系数翻转,即

\[F(\frac1x)=G(\frac1x)Q(\frac1x)+R(\frac{1}x) \]

此时我们再看看对应的多项式的系数范围:

\(F(\frac1x)\)次数\(\left[-n,0\right]\),共\(n+1\)项

\(G(\frac1x)\)次数\(\left[-m,0\right]\),共\(m+1\)项

\(Q(\frac1x)\)次数\(\left[m-n,0\right]\),共\(n-m+1\)项

\(R(\frac1x)\)次数\(\left[1-m,0\right]\),共\(m\)项

而接下来为了计算便利我们得把次数都化为自然数,那么两边同时乘以\(x^n\)得

\[x^nF(\frac1x)=x^mG(\frac1x)*x^{n-m}Q(\frac1x)+x^nR(\frac1x) \]

看出来这样划分的深意和妙处了吗?接下来我们再看看一些多项式的系数范围:

\(x^nF(\frac1x)\)次数\(\left[0,n\right]\),共\(n+1\)项

\(x^mG(\frac1x)\)次数\(\left[0,m\right]\),共\(m+1\)项

\(x^{n-m}Q(\frac1x)\)次数\(\left[0,n-m\right]\),共\(n-m+1\)项

\(x^nR(\frac1x)\)次数\(\left[n-m+1,n\right]\),共\(m\)项

接下来我们不妨设\(\begin{cases}F^{'}(x)=x^nF(\frac1x)\\G^{'}(x)=x^mG(\frac1x)\\Q^{'}(x)=x^{n-m}Q(\frac1x)\\R^{'}(x)=x^nR(\frac1x) \end{cases}\),那么很显然F'、G'、Q'分别对应着F、G、Q系数翻转之后的多项式,而R'则与我们所需要的Q'的系数完全错开,于是我们在\(\pmod{x^{n-m+1}}\)意义下可以刚好完全消去R'而完全不影响Q'的任何一项,即

\[F^{'}(x)\equiv G^{'}(x)Q^{'}(x)\pmod{x^{n-m+1}}\Leftrightarrow Q^{'}(x)\equiv F^{'}(x)G^{'-1}(x)\pmod{x^{n-m+1}} \]

于是我们求出Q'之后再将系数翻转便可以得到我们所需要的Q,之后的R便可通过\(R(x)=F(x)-Q(x)G(x)\)求出,而求逆得到\(Q^{'-1}(x)\)的时候一定要注意是对\(\pmod{x^{n-m+1}}\)的逆元

03 代码实现

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int xx=(1<<21)+1,mod=998244353;
const int g1=3,g_1=332748118;
int rev[xx],ite[21],sup;

inline ll power(ll x,int y){ll ans=1;for(;y;(x*=x)%=mod,y>>=1) if(y&1) (ans*=x)%=mod;return ans;}
inline void init(int n)
{
    memset(rev,0,sizeof(rev));
    for(sup=1;sup<=n;sup<<=1);
    for(n=sup>>1;n;n>>=1)
        for(register int i=n;i<sup;i+=(n<<1))
            for(register int j=0;j<n;++j)
                rev[i+j]+=(sup/n/2);
}
inline void ntt(ll *a,const bool op)
{
    for(register int i=0;i<sup;++i)
        if(i<rev[i])
            swap(a[i],a[rev[i]]);
    for(register int k=1;k<sup;k<<=1)
    {
        const ll ge=power(op?g1:g_1,(mod-1)/(k<<1));
        for(register int i=0;i<sup;i+=(k<<1))
        {
            ll gj=1;
            for(register int j=0;j<k;++j,(gj*=ge)%=mod)
            {
                const ll l=a[i+j],r=(gj*a[i+j+k])%mod;
                a[i+j]=(l+r)%mod;
                a[i+j+k]=(l-r+mod)%mod;
            }
        }
    }
}
ll tmp1[xx],tmp2[xx];
inline void inv(ll *f,ll *g,int n)
{
    g[0]=power(f[0],mod-2);
    for(;n>1;(n+=1)>>=1)
        ite[++ite[0]]=n;
    ite[ite[0]+1]=1;
    for(register int i=ite[0];i;--i)
    {
        init(ite[i]+ite[1]);
        for(register int j=0;j<sup;++j)
        {
            tmp1[j]=f[j];
            if(j<ite[i+1])
                tmp2[j]=g[j];
            else
                tmp2[j]=0;
        }
        ntt(tmp1,1),ntt(tmp2,1);
        for(register int j=0;j<sup;++j)
            tmp1[j]=tmp2[j]*((2-tmp1[j]*tmp2[j]%mod+mod)%mod)%mod;
        ntt(tmp1,0);
        ll inv=power(sup,mod-2);
        for(register int j=0;j<ite[1];++j)
            g[j]=tmp1[j]*inv%mod;
    }
}

ll F[xx],G[xx],Q[xx],R[xx];
ll Fr[xx],Gr[xx],invGr[xx];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    ++n,++m;
    for(register int i=0;i<n;++i)
        scanf("%lld",&F[i]),Q[n-i-1]=F[i];
    for(register int i=0;i<m;++i)
        scanf("%lld",&G[i]),Gr[m-i-1]=G[i];
    for(register int i=n-m+1;i<m;++i)
        Gr[i]=0;
    inv(Gr,invGr,n-m+1);
    init(n<<2);
    ntt(Q,1),ntt(invGr,1);
    for(register int i=0;i<sup;++i)
        Q[i]=Q[i]*invGr[i]%mod;
    ntt(Q,0);
    ll inv=power(sup,mod-2);
    for(register int i=0;i<sup;++i)
        Q[i]=Q[i]*inv%mod;
    for(register int i=0;i<n-m-i;++i)
        swap(Q[i],Q[n-m-i]);
    for(register int i=0;i<n-m+1;++i)
        printf("%lld ",Q[i]);
    puts("");
    for(register int i=n-m+1;i<sup;++i)
        Q[i]=0;
    ntt(F,1),ntt(G,1),ntt(Q,1);
    for(register int i=0;i<sup;++i)
        R[i]=(F[i]-G[i]*Q[i]%mod+mod)%mod;
    ntt(R,0);
    for(register int i=0;i<m-1;++i)
        printf("%lld ",R[i]*inv%mod);
    puts("");
    system("pause");
    return 0;
}

\(part~ 2 ~\)常系数齐次线性递推

01 问题描述

求一个满足 \(k\) 阶齐次线性递推数列 \({a_i}\) 的第 \(n\) 项,即:

\[a_n=\sum\limits_{i=1}^{k}f_i \times a_{n-i} \]

输入格式

第一行两个数 \(n\),\(k\),如题面所述。

第二行 \(k\) 个数,表示 \(f_1 \ f_2 \ \cdots \ f_k\)

第三行 \(k\) 个数,表示 \(a_0 \ a_1 \ \cdots \ a_{k-1}\)

输入格式

一个数,表示 \(a_n \bmod 998244353\) 的值

样例 #1
样例输入 #1
6 4
3 -1 0 4
-2 3 1 5
样例输出 #1
73
提示

对于100%的数据我们有$N \leqslant 10^{9} , K \leqslant 32000 $

保证读入的数字均为 \([-10^9,10^9]\) 内的整数。

02 问题转化

首先我们可以发现这个数列显然是可以通过矩阵构造转化的

首先我们假设如下矩阵:

\[\alpha_i=\begin{pmatrix}a_i\\a_{i+1}\\\vdots\\a_{i+k-1}\end{pmatrix}^T,\beta=\begin{pmatrix}0&0&0&\cdots&0&f_k\\1&0&0&\cdots&0&f_{k-1}\\0&1&0&\cdots&0&f_{k-2}\\\vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&0&\cdots&0&f_2 \\0&0&0&\cdots&1&f_{1} \end{pmatrix} \]

那么根据矩阵乘法的定义以及我们的递推关系式,我们可以得到

\[\alpha_i\beta=\alpha_{i+1} \]

于是

\[\alpha_n=\alpha_0\beta^n \]

对于\(\alpha_0\)则是输入数据\(a_{0-(k-1)}\)组成的列向量,而\(\alpha_n\)的第一项则是我们所需要求的\(a_n\),因此我们的任务可以说直接转化成了求解\(\alpha_0\beta^n\),其中最核心的一点就是求解\(\beta^n\)

然而对于求解一个矩阵,或者说方阵的n次幂,我们的办法最直接的一项便是计算机直接利用矩阵乘法的定义进行求解,这样求解的复杂度是\(O(k^3n)\). 而由于矩阵乘法的结合律我们可以利用快速幂的思想将这个复杂度化为\(O(k^3\log_2n)\),然而对这个问题来说这里最大的问题就在于\(k\)这一维也十分难办,不是很好降维,于是我们考虑其他办法

03 特征多项式

我们对于求解一个方阵的n次幂,由线性代数中学到的知识会首先想到将这个方阵给相似对角化然后对应的我们就可以求解其对角矩阵的n次幂之后便可直接求出我们所需要的数值

然而对于这道题而言我们的这种想法也受到了限制,我们假设我们将所需要的转移矩阵给相似对角化如下

\[\beta=P^{-1}\Lambda P \]

然而对于我们所目标的矩阵\(\beta^n\)我们可以由\(O(k\log_2n)\)的复杂度求解出\(\Lambda^n\),然而对于后续的矩阵乘法操作无论是\(P^{-1}\Lambda^nP\)还是\(\alpha\beta^n\),尽管再怎么优化我们也还是会至少有\(O(k^2)\)的复杂度,对于这道题的范围来说十分不友好,更别说对于一个特殊的\(\beta\)我们能不能在实数范围内顺利将其相似对角化,这是一个十分有难度的问题

然而这个思路也给我们提供了一个十分巧妙的转化想法,无论怎样我们首先的方向是尽量将这个矩阵的特征多项式给求出,只有这样我们才能观察出来相似对角化的可行性

而通过数学归纳法我们最终可以得到我们的特征多项式如下

\[g(\lambda)=(-1)^{k+1}\sum\limits_{i=1}^kf_i\lambda^{k-i}+(-1)^k\lambda^k \]

\[g(\lambda)=(-1)^{k+1}(-\lambda^k+\sum\limits_{i=1}^kf_i\lambda^{k-i}) \]

而由我们线性代数中学到的特征值的相关性质我们可以知道,由于\(g(\lambda)=0\),那么

\[g(\beta)=(-1)^{k+1}(-\beta^k+\sum\limits_{i=1}^kf_i\beta^{k-i})=0 \]

于是通过我们之前得到的多项式除法的相关知识,我们一定能得到

\[\beta^n=Q(\beta)g(\beta)+R(\beta)=R(\beta)\,\,\cdots\cdots ① \]

其中\(R(\beta)\)是\(\beta^n\pmod{g(\beta)}\)的余项,可以表示为\(\beta^{0-(k-1)}\)的线性组合

之后最优秀的一步转化,就是在①式两边同时左乘一个\(\alpha_0\),那么

\[\alpha_0\beta^n=\alpha_0 R(\beta) \]

而由我们之前所说的\(R(\beta)=\left<\beta^0,\beta^1,\beta^2,\cdots,\beta^{k-1}\right>\),可以得到

\[\alpha_n=\left<\alpha\beta^0,\alpha\beta^1,\alpha\beta^2,\cdots,\alpha\beta^{k-1}\right>=\left<\alpha_0,\alpha_1,\alpha_2,\cdots,\alpha_{k-1}\right> \]

而由我们向量相等的充要条件,我们可以直接得到

\[a_n=\left<a_0,a_1,a_2,\cdots,a_{k-1}\right> \]

且线性表示的系数与我们最开始得到的\(\left<\beta^0,\beta^1,\beta^2,\cdots,\beta^{k-1}\right>\)的系数相同

04 快速幂的思想依旧

而我们使用多项式除法的话复杂度会在\(O((n+k)\log_2(n+k))\),而这也成为了我们最后一个障碍,也就是过大的n应该怎么处理

而对于这个我们会用到我们的老思路也就是快速幂,因为我们被除的整式的结构十分简单也就是\(x^n\),那么我们可以轻松地利用快速幂的思路将其转化为\(x^1,x^2,x^4,\cdots,x^{2^m}\)的乘积,最后面我们可以大大缩短我们所需要进行多项式除法的被除式的长度,可以保持每次的多项式除法中的被除式的长度不会超过2k,那么一次多项式除法的复杂度不会超过\(\Theta(3k\log_2 3k)\),而快速幂的乘法次数不会超过\(\Theta(2\log_2n)\),那么最终的时间复杂度化为\(O(k\log_2k\log_2n)\),相较于\(O(k^3\log_n)\)大大简化

当然k的大小还是制约我们算法的最大限制,但能将其化为线性复杂度已经能够解决极大一部分的问题了

最后面还有一个小的优化细节,那就是我们每次多项式除法的除式都是相同的,那么我们除式对应的翻转逆也可以预处理求出,这样可以大大节省我们用在多项式除法中的费用

05 程序实现

最后的程序实现一定要注意由于我们需要进行多次多项式除法,有些数组需要及时清理,不然无法保证多项式除法多次执行的正确性

#include<bits/stdc++.h>
using namespace std;
#define cl(a) memset(a,0,sizeof(a))
typedef long long ll;
const int xx=(1<<17)+10,mod=998244353;
const int g1=3,_g=332748118;
int rev[xx],ite[21],sup;

inline int read()
{
    char ch=getchar();
    int x=0,f=1;
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
    return f*x;
}
inline ll power(ll x,int y)
{
    ll ans=1;
    for(;y;(x*=x)%=mod,y>>=1)
        if(y&1)
            (ans*=x)%=mod;
    return ans;
}
inline void init(int n)
{
    memset(rev,0,sizeof(rev));
    for(sup=1;sup<=n;sup<<=1);
    for(register int k=sup>>1;k;k>>=1)
    {
        n=(sup/k)>>1;
        for(register int i=k;i<sup;i+=(k<<1))
            for(register int j=0;j<k;++j)
                rev[i+j]+=n;
    }
}
inline void ntt(ll *a,const bool op)
{
    for(register int i=0;i<sup;++i)
        if(i<rev[i])
            swap(a[i],a[rev[i]]);
    for(register int k=1;k<sup;k<<=1)
    {
        const ll ge=power(op?g1:_g,(mod-1)/(k<<1));
        for(register ll i=0;i<sup;i+=(k<<1))
        {
            ll gj=1;
            for(register int j=0;j<k;++j,(gj*=ge)%=mod)
            {
                const ll l=a[i+j],r=(gj*a[i+j+k])%mod;
                a[i+j]=(l+r)%mod;
                a[i+j+k]=(l-r+mod)%mod;
            }
        }
    }
}
inline void finish(ll *a)
{
    ntt(a,0);
    ll invsup=power(sup,mod-2);
    for(register int i=0;i<sup;++i)
        (a[i]*=invsup)%=mod;
}
ll tmp1[xx],tmp2[xx];
inline void inv(ll *a,ll *b,int n)
{
    cl(ite);
    b[0]=power(a[0],mod-2);
    for(;n>1;n=(n+1)>>1)
        ite[++ite[0]]=n;
    ite[ite[0]+1]=1;
    for(register int i=ite[0];i;--i)
    {
        init(ite[i]<<1);
        for(register int j=0;j<sup;++j)
        {
            tmp1[j]=j<ite[i]?a[j]:0;
            tmp2[j]=j<ite[i+1]?b[j]:0;
        }
        ntt(tmp1,1),ntt(tmp2,1);
        for(register int j=0;j<sup;++j)
            tmp1[j]=tmp2[j]*((2-tmp1[j]*tmp2[j]%mod+mod)%mod)%mod;
        finish(tmp1);
        for(register int j=0;j<ite[i];++j)
            b[j]=tmp1[j];
    }
}
ll tmp3[xx],tmp4[xx],tmp5[xx],tmp6[xx];
inline void getmod(ll *a,int n,ll *b,int m)
{
    if(n<m) return;
    cl(tmp3);cl(tmp4);cl(tmp5);
    for(register int i=0;i<n;++i)
        tmp3[i]=a[n-i-1];
    for(register int i=0;i<n-m+1;++i)
        tmp5[i]=tmp6[i];
    init(2*n-m+1);
    ntt(tmp3,1),ntt(tmp5,1);
    for(register int i=0;i<sup;++i)
        tmp4[i]=(tmp3[i]*tmp5[i])%mod;
    finish(tmp4);
    for(register int i=0;i<sup;++i)
    {
        if(i<m) tmp5[i]=b[i];
        else tmp5[i]=0;
        if(i<n-m+1) tmp3[i]=tmp4[n-m-i];
        else tmp3[i]=0;
    }
    ntt(tmp3,1),ntt(a,1),ntt(tmp5,1);
    for(register int i=0;i<sup;++i)
        a[i]=(a[i]-tmp5[i]*tmp3[i]%mod+mod)%mod;
    finish(a);
    for(register int i=m-1;i<sup;++i)
        a[i]=0;
}
ll Ans[xx],kid[xx];
inline void PoW(ll *a,int n,ll *Mod,int m)
{
    Ans[0]=1;
    for(register int x=2,y=1;n;n>>=1)
    {
        if(n&1)
        {
            init(y+=x);
            for(register int i=0;i<sup;++i)
                kid[i]=a[i];
            ntt(Ans,1),ntt(kid,1);
            for(register int i=0;i<sup;++i)
                (Ans[i]*=kid[i])%=mod;
            finish(Ans);
            getmod(Ans,y,Mod,m);
            if(y>m) y=m-1;
        }
        init(x+=x);
        ntt(a,1);
        for(register int i=0;i<sup;++i)
            (a[i]*=a[i])%=mod;
        finish(a);
        getmod(a,x,Mod,m);
        if(x>m) x=m-1;
    }
}
ll f[xx>>1],a[xx>>1],tmp[xx];
int main()
{
    int n,k;
    n=read();f[k=read()]=mod-1;
    for(register int i=k-1;~i;--i)
    {
        f[i]=read();
        if(f[i]<0)
            f[i]+=mod;
    }
    for(register int i=0;i<k+1;++i)
        tmp4[i]=f[k-i];
    inv(tmp4,tmp6,k);
    for(register int i=0;i<k;++i)
    {
        a[i]=read();
        if(a[i]<0)
            a[i]+=mod;
    }
    tmp[1]=1;
    PoW(tmp,n,f,k+1);
    ll ans=0;
    for(register int i=0;i<k;++i)
        (ans+=(Ans[i]*a[i]%mod))%=mod;
    printf("%lld\n",ans);
    return 0;
}

标签:int,多项式,frac1x,beta,应用,除法,我们,left
From: https://www.cnblogs.com/cjsyh/p/17066348.html

相关文章

  • 多项式求逆
    $part~1~$多项式求逆(乘法逆元QwQ)01题目描述给定一个多项式\(F(x)\),请求出一个多项式\(G(x)\),满足\(F(x)*G(x)\equiv1\pmod{x^n}\)。系数对\(998244353\)......
  • 多项式基础运算
    多项式全家桶,但是这个做标题有失风雅。很多地方的严谨证明因为水平不足略过,等之后数学水平提高再回来修正。一些有用的资料:NTT与多项式全家桶-command_block的博客......
  • 芯片设计步骤和EDA的应用
    前端设计一、第一步,明确市场需求,确定产品的功能和性能。 二、第二步,定义芯片的算法和模块(IP核)。 三、第三步,搭建功能模块。用硬件描述语言(HDL)将各个功能模块用代码......
  • 数论分块(除法分块)
    定义数论分块是个很常见的技巧,常用于计算$$\sum_{i=1}^{n}\left[\frac{k}{i}\right]$$思路原理很简单:设\(t_i\in\{x|x=\left[\frac{k}{i}\right]\}\)我们想办法每次......
  • BurpSuite使用指南-综合应用
    综合应用在很多实际的情况下,我们需要使用BP工具进行信息的拦截,使用其他工具进行生产测试用例,分析数据。其中常见的工具为CO2打开BP找到“extender”选择bapp stroe在旁边的......
  • 【Windows】应用软件注册表位置
    ✨应用软件注册表位置搜索注册表编辑器或者regedit在注册表编辑器中,定位到HKEY_CURRENT_USER\Software大部分用户安装的应用软件注册表都在这个地方可以根据软件名......
  • MySQL 日期函数、时间函数在实际场景中的应用
    整理日常业务中用到日期函数的一些场景,并对日期函数按照使用类型做了分类,实例也尽可能符合日常需求。为了方便查阅,可以先看目录,再根据需要看具体方法和实例。首先明确日期......
  • Mac应用程序无法打开或文件损坏的处理方法
    很多用户在安装盗版Mac软件的时候,经常会遇到提示“xxx.app已损坏,打不开。您应该将它移到废纸篓“或”打不开的xxx.app,因为它来自身份不明的开发者”,等多种打不开盗版软件......
  • Service Worker 在 PWA 中的应用
    有些开发人员认为,使用Web应用程序PWA特性的最大收益是应用程序安装横幅,即appinstallbanners.开发人员可以通过正确的启发式方法(hittingtherightheuristics)获......
  • SAP Fiori Belize 主题应用在 SAPGUI 里的一些要点
    为了遵守Fiori设计指南,SAPGUI里的Belize主题需要在某些方面与之前提供的所有SAPGUIforWindows/HTML主题不同。为了更好地了解屏幕上的各种功能,FioriBelizeDes......