1 条题解

  • 0
    @ 2025-8-24 22:02:59

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar BJpers2
    **

    搬运于2025-08-24 22:02:59,当前版本为作者最后更新于2020-06-30 21:06:15,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    本问题的大致做法基本上是全网统一的,都是快速幂求xnx^n对特征多项式pp取模的结果。

    翻阅网上的题解,我发现它们对这种做法的解释都用到了线性代数内容,包括矩阵特征值,行列式,C-H定理等。

    这些内容对于有基础的选手来说并不至于非常艰深,然而不可否认的是,一个事先并没有了解过这些线性代数内容的选手,面对一篇充满全新概念的题解,很可能会产生理解的障碍。

    事实上,我们并不需要使用任何线性代数的知识来导出这个做法。


    1. 返璞归真

    让我们忘掉矩阵,行列式和特征值,采用小学层面的方法求斐波那契数列(一个经典的例子)的第nn项。

    我们换一个角度,倒过来求。假设我们现在欲求fib5fib_5

    我们可以列出如下过程:$fib_5=fib_4+fib_3=2fib_3+fib_2=3fib_2+2fib_1=5fib_1+3fib_0$

    最后带入f0,f1f_0,f_1计算即可。

    这当中的推导过程不是什么秘密,不过是每次取最前面那一项拿递推式展开罢了。

    2. 本质

    考虑一下上述过程本质上究竟干了什么,你可以先停一分钟自己好好思考一下。如果你想到了,本题解对你来说就已经没有阅读的价值。

    上述过程,就是通过每次消去最高次项,求x5x^5对斐波那契数列的特征多项式——x2x1x^2-x-1取模的结果的过程。

    如果你还没有反应过来,我可以展示一下:

    $$\begin{aligned} & 0x^0+0x^1+0x^2+0x^3+0x^4+1x^5 \\ \xrightarrow{-1x^3(x^2-x-1)} & 0x^0+0x^1+0x^2+1x^3+1x^4 \\ \xrightarrow{-1x^2(x^2-x-1)} & 0x^0+0x^1+1x^2+2x^3 \\ \xrightarrow{-2x^1(x^2-x-1)} & 0x^0+2x^1+3x^2 \\ \xrightarrow{-3x^0(x^2-x-1)} & 3x^0+5x^1 \end{aligned} $$

    两者之间的逻辑关联很简单,因为前者是每次取和式里下标最大那一项,替换成两个下标较小的项之后加回去;而后者是每次从多项式里减去若干倍的x2x^2,并替换成相应倍数的x+1x+1之后加回去。

    3. 推广

    理解了上述逻辑之后,我们能轻易将其推广到一般线性其次递推式的情形:

    fi=p1fi1+p2fi2++pkfikf_i=p_1f_{i-1}+p_2f_{i-2}+\dots+p_kf_{i-k}

    要求fnf_n,只要求多项式xnx^n

    p(x)=xkp1xk1p2xk2pkx0p(x)=x^k-p_1x^{k-1}-p_2x^{k-2}-\dots-p_kx^0

    取模的结果。因为「多项式取模」的过程恰好与「每次按递推式展开下标最大的那项」的过程一致,都可以用「替换」来解读。

    4. 多项式取模

    接下来基本上就是多项式取模的板子,会多项式取模的可以直接跳过。

    我们写一个快速幂,求多项式xxnn次方,只不过每次乘法都对上面提到的特征多项式p(x)p(x)取模。

    那么需要解决的问题变为快速求一个l=2k2l=2k-2次多项式对一个kk次多项式取模的结果。

    也即需把一个ll次多项式aa展开成a=pq+ra=pq+r的形式,其中qq是商,rr是余数(也就是我们的目标)。

    我们设aRa^R为多项式aa的反转(第ll项和第00项互换,第l1l-1项和第11项互换,以此类推),易得aRpRqRmodxlk+1a^R\equiv p^Rq^R \bmod x^{l-k+1}

    因此我们只需求出pRp^R在模xlk+1x^{l-k+1}意义下的逆元,乘上aRa^R即可得到qRq^R。最后利用r=apqr=a-pq即可求出我们需要的rr

    5. 后记

    本片题解介绍的思路应该有很多前人想到,但是令我比较吃惊的是没有人把它发表出来(或者是发表了我没有看到)。希望这篇题解能让初次接触线性递推的选手不要被纷繁复杂的数学公式劝退,少走弯路,更深入地理解这个算法。

    6. 代码

    稍微有点压行。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #define FF FOF(i,0,L)
    #define FOR(i,a,b) for(int i=a;i<=b;i++)
    #define FOF(i,a,b) for(int i=a;i< b;i++)
    #define ROF(i,a,b) for(int i=a;i>=b;i--)
    #define CON(a,b) DFT(a,L);FF a[i]=1ll*a[i]*b[i]%P;IFT(a,L);
    using namespace std;
    const int N=150150,P=998244353;
    int n,m,K,L,o,A,Q;
    int rv[N],W[N],p[N],f[N],t[N],a[N],b[N];
    void ipt(int&x){scanf("%d",&x);x%=P;x+=x>>31&P;}
    int qpw(int x,int y){int z=1;for(;y;y>>=1,x=1ll*x*x%P)if(y&1) z=1ll*z*x%P;return z;}
    void ini(int n){
    	for(L=1;L<=n;L<<=1,o++);
    	FF rv[i]=rv[i>>1]>>1|(i&1)<<(o-1);
    	int V=qpw(3,P>>o);W[L>>1]=1;
    	FOF(i,(L>>1)+1,L) W[i]=1ll*W[i-1]*V%P;
    	ROF(i,(L>>1)-1,1) W[i]=W[i<<1];
    }
    void DFT(int*A,int L){
    	static unsigned long long B[N];
    	int u=o-__builtin_ctz(L),t;
    	FF B[i]=A[rv[i]>>u];
    	for(int i=1;i<L;i<<=1)for(int j=0,s=i<<1;j<L;j+=s)FOF(k,0,i)
    		t=B[i+j+k]*W[i+k]%P,B[i+j+k]=B[j+k]+P-t,B[j+k]+=t;
    	FF A[i]=B[i]%P;
    }
    void IFT(int*A,int L){
    	reverse(A+1,A+L);DFT(A,L);
    	int V=P-(P-1)/L;
    	FF A[i]=1ll*A[i]*V%P;
    }
    int upl(int n){return 1<<(32-__builtin_clz(n));}
    void INV(int*A,int*B,int n){
    	static int C[N],L;
    	if(!n) return B[0]=qpw(A[0],P-2),void();
    	INV(A,B,n>>1);L=upl(n<<1);
    	FF C[i]=i>n?0:A[i];
    	DFT(C,L);DFT(B,L);
    	FF B[i]=(2-1ll*C[i]*B[i]%P+P)*B[i]%P;IFT(B,L);
    	FF B[i]=i>n?0:B[i],C[i]=0;
    }
    void MUL(int*A,int*B){
    	static int C[N];
    	FF C[i]=A[i];DFT(C,L);
    	CON(B,C);
    	FF C[i]=i>K?0:B[n-i];
    	CON(C,t);
    	FF C[i]=i>K?0:C[i];
    	reverse(C,C+K+1);
    	CON(C,p);
    	FF (B[i]+=P-C[i])%=P;
    }
    int main(){
    	scanf("%d%d",&Q,&m);
    	ini(m<<1);n=m-1<<1;K=n-m;
    	FOR(i,1,m) ipt(p[i]);
    	FOF(i,0,m) ipt(f[i]);
    	p[0]=P-1;
    	INV(p,t,K);DFT(t,L);
    	reverse(p,p+m+1);DFT(p,L);
    	a[1]=b[0]=1;
    	for(;Q;Q>>=1,MUL(a,a))if(Q&1) MUL(a,b);
    	FOF(i,0,m) (A+=1ll*b[i]*f[i]%P)%=P;
    	cout<<A<<'\n';
    }
    
    • 1

    信息

    ID
    3706
    时间
    3000ms
    内存
    256MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者