首页 > 其他分享 >常系数齐次线性递推

常系数齐次线性递推

时间:2023-02-04 08:55:15浏览次数:73  
标签:int wl 150010 齐次 ans 线性 include 递推 mod

现在终于把线性递推常数减小了许多。(实际上我原先写的多项式取模才这么慢)

Fiduccia 算法

一个方法是求 \(x^n\) 对递推数列的特征多项式

\[p(x)=x^k-p_1x^{k-1}-p_2x^{k-2}-\cdots-p_k \]

取模的结果,然后逐项代入即可求得答案。这个取模可以像普通的快速幂一样,每次对 \(p(x)\) 取模。证明不会。

但是这玩意跑的非常慢,卡了半天常仍然只能做到每个点 1.7s 的水平。当然我本身大常数选手。

这玩意当然不是重点。放个代码仅供参考。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const int mod=998244353,g=3;
int n,k,wl,ans,a[150010],b[150010],r[150010],inv[150010],res[150010];
int q[150010],f[150010],c[150010],p[150010],tmp[150010];
int val[150010];
void get(int n){
    wl=1;
    while(n>=wl)wl<<=1;
    for(int i=0;i<=wl;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(__lg(wl)-1));
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*a*ans%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
const int invg=qpow(g,mod-2);
void ntt(int a[],int n,int tp){
    for(int i=1;i<n;i++)if(i<r[i])swap(a[i],a[r[i]]);
    for(int mid=1;mid<n;mid<<=1){
        int wn=qpow(tp==1?g:invg,(mod-1)/(mid<<1));
        for(int j=0;j<n;j+=mid<<1){
            int w=1;
            for(int k=0;k<mid;k++,w=1ll*w*wn%mod){
                int x=a[j+k],y=1ll*w*a[j+mid+k]%mod;
                a[j+k]=(x+y)%mod;a[j+mid+k]=(x-y+mod)%mod;
            }
        }
    }
    if(tp^1){
        int inv=qpow(n,mod-2);
        for(int i=0;i<n;i++)a[i]=1ll*a[i]*inv%mod;
    }
}
void getinv(int n,int a[],int b[]){
    if(n==1){
        b[0]=qpow(a[0],mod-2);
        return;
    }
    getinv((n+1)>>1,a,b);
    get(n<<1);
    for(int i=0;i<n;i++)c[i]=a[i];
    for(int i=n;i<wl;i++)c[i]=0;
    ntt(b,wl,1);ntt(c,wl,1);
    for(int i=0;i<wl;i++)b[i]=1ll*(2-1ll*b[i]*c[i]%mod+mod)%mod*b[i]%mod;
    ntt(b,wl,-1);
    for(int i=n;i<wl;i++)b[i]=0;
}
void getdiv(int n,int m,int a[],int b[]){
    get(n<<1);
    for(int i=0;i<wl;i++)q[i]=res[i]=c[i]=0;
    for(int i=0;i<=n;i++)q[n-i]=a[i];
    ntt(q,wl,1);
    for(int i=0;i<wl;i++)q[i]=1ll*q[i]*inv[i]%mod;
    ntt(q,wl,-1);
    for(int i=n-m+1;i<wl;i++)q[i]=0;
    reverse(q,q+n-m+1);
    ntt(b,wl,1);ntt(q,wl,1);
    for(int i=0;i<wl;i++)b[i]=1ll*b[i]*q[i]%mod;
    ntt(b,wl,-1);
    for(int i=0;i<m;i++)res[i]=(a[i]-b[i]+mod)%mod;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++)scanf("%d",&p[i]),p[i]=(p[i]+mod)%mod;
    reverse(p,p+k+1);p[k]=1;
    for(int i=0;i<k;i++)p[i]=(mod-p[i])%mod;
    for(int i=0;i<=k;i++)tmp[i]=f[k-i]=p[i];
    getinv(k-1,f,inv);
    get(k<<2);ntt(inv,wl,1);
    for(int i=0;i<k;i++)scanf("%d",&val[i]),val[i]=(val[i]+mod)%mod;
    b[1]=a[0]=1;
    while(n){
        if(n&1){
            get(k<<1);
            ntt(a,wl,1);ntt(b,wl,1);
            for(int i=0;i<wl;i++)a[i]=1ll*a[i]*b[i]%mod;
            ntt(a,wl,-1);ntt(b,wl,-1);
            getdiv((k-1)<<1,k,a,p);
            memcpy(a,res,sizeof(a));
            memcpy(p,tmp,sizeof(p));
        }
        get(k<<1);
        ntt(b,wl,1);
        for(int i=0;i<wl;i++)b[i]=1ll*b[i]*b[i]%mod;
        ntt(b,wl,-1);
        getdiv((k-1)<<1,k,b,p);
        memcpy(b,res,sizeof(b));
        memcpy(p,tmp,sizeof(p));
        n>>=1;
    }
    for(int i=0;i<k;i++)ans=(ans+1ll*val[i]*a[i])%mod;
    printf("%d\n",ans);
    return 0;
}

LSB-first 算法

重点来了。首先这玩意只能在模数是质数的情况下使用。

它的思想在于把递推数列转为生成函数的形式:

\[F(x)=\frac{P(x)}{Q(x)} \]

其中 \(Q(x)\) 是递推式的生成函数(就是特征多项式的系数翻转),而 \(P(x)\) 是一个次数小于 \(k\) 的多项式。

一个结论是一定存在这样的一个 \(P(x)\)。证明还是不会。那么我们就可以用 \(F(x)\) 的前 \(k\) 项和 \(Q(x)\) 计算 \(P(x)\) 的前 \(k\) 项。

然后考虑如何提取 \([x^n]\frac{P(x)}{Q(x)}\)。我们使用 Bostan-Mori 算法解决这个问题。

该算法依赖于

\[\frac{P(x)}{Q(x)}=\frac{P(x)Q(-x)}{Q(x)Q(-x)} \]

这玩意分母显然只有偶数次项不是 \(0\) ,那么求逆之后也是只有偶数次项不是 \(0\),也就是分子的奇数次项和偶数次项互不影响。那假如说我们要提取的 \(x^n\) 的 \(n\) 是奇数的话,那么分子的偶数次项就可以全清掉。反之同理。这样每次做一次多项式乘法然后对分子分母的奇/偶次项重新标号,就可以把 \(n\) 减半。同时由于多项式乘法使得次数变成 \(2k\),这样重标号之后次数就仍然是 \(k\)。复杂度和上边的一样都是 \(O(k\log k\log n)\)。

这玩意非常好写而且跑的很快,我写的这份时间空间都是上边那个的四分之一,450ms。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const int mod=998244353,g=3;
int n,k,wl,ans,f[150010],r[150010];
int q[150010],p[150010];
void get(int n){
    wl=1;
    while(n>=wl)wl<<=1;
    for(int i=0;i<=wl;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(__lg(wl)-1));
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*a*ans%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
const int invg=qpow(g,mod-2);
void ntt(int a[],int n,int tp){
    for(int i=1;i<n;i++)if(i<r[i])swap(a[i],a[r[i]]);
    for(int mid=1;mid<n;mid<<=1){
        int wn=qpow(tp==1?g:invg,(mod-1)/(mid<<1));
        for(int j=0;j<n;j+=mid<<1){
            int w=1;
            for(int k=0;k<mid;k++,w=1ll*w*wn%mod){
                int x=a[j+k],y=1ll*w*a[j+mid+k]%mod;
                a[j+k]=(x+y)%mod;a[j+mid+k]=(x-y+mod)%mod;
            }
        }
    }
    if(tp^1){
        int inv=qpow(n,mod-2);
        for(int i=0;i<n;i++)a[i]=1ll*a[i]*inv%mod;
    }
}
int main(){
    scanf("%d%d",&n,&k);q[0]=1;
    for(int i=1;i<=k;i++){
        int x;scanf("%d",&x);x=(x+mod)%mod;
        q[i]=(mod-x)%mod;
    }
    for(int i=0;i<k;i++){
        int x;scanf("%d",&x);p[i]=(x+mod)%mod;
    }
    for(int i=0;i<=k;i++)f[i]=q[i];
    get(k<<1);
    ntt(p,wl,1);ntt(f,wl,1);
    for(int i=0;i<wl;i++)p[i]=1ll*p[i]*f[i]%mod;
    ntt(p,wl,-1);
    for(int i=k;i<wl;i++)p[i]=0;
    while(n){
        for(int i=0;i<=k;i++)f[i]=(i&1)?(mod-q[i])%mod:q[i];
        for(int i=k+1;i<wl;i++)f[i]=0;
        ntt(f,wl,1);ntt(p,wl,1);ntt(q,wl,1);
        for(int i=0;i<wl;i++)p[i]=1ll*p[i]*f[i]%mod,q[i]=1ll*q[i]*f[i]%mod;
        ntt(p,wl,-1);ntt(q,wl,-1);
        for(int i=0;i<=k;i++)p[i]=p[(i<<1)|(n&1)],q[i]=q[i<<1];
        for(int i=k+1;i<wl;i++)p[i]=q[i]=0;
        n>>=1;
    }
    ans=1ll*p[0]*qpow(q[0],mod-2)%mod;
    printf("%d\n",ans);
    return 0;
}

标签:int,wl,150010,齐次,ans,线性,include,递推,mod
From: https://www.cnblogs.com/gtm1514/p/17090840.html

相关文章

  • 算法学习笔记(12): 线性基
    线性基熟练掌握异或运算是食用本文的大前提,请读者留意是什么?是一种利用线性代数的知识,用于解决异或问题的一种手段(不能算作数据结构吧这)本文并不会涉及到线性代数。......
  • 线性代数整理(upd:2023.2.3)
    线性代数byAmanoKumiko1.行列式(1)行列式交换两行(列),行列式取反(2)行列式某一行(列)加上另一行(列)的\(k\)倍,行列式不变(3)行列式某一行(列)提出公因数\(k\),行列式乘上\(......
  • 万字详解递归与递推
    前言相信这个故事,朋友们应该都不陌生,从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?「从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事......
  • 线性表的顺序存储
    一、线性表简介  线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列。线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直......
  • 线性表的链接储存结构及实现
    单链表1单链表的定义单链表是用一组任意的存储单元来存放线性表的元素。存储单元可以连续,也可以不连续为了正确的表示元素之间的逻辑关系,每个存储单元除了存储元素外,还需......
  • 线性基求交
    voidmerge(Vectorb){ Vectorc=*this; Vectorres; for(inti=0;i<=63;++i){ if(b[i]){ ullv=b[i],s=0; for(intj=63;~j;--j){ ......
  • Ceres-Solver求解非线性最小二乘问题
    non-linearleastsquareproblemhttp://ceres-solver.org/nnls_tutorial.htmlhowtoregularizednonlinearleastsquares?https://groups.google.com/g/ceres-so......
  • 机器学习-白板推导-系列(三)笔记:线性回归最小二乘法与正则化岭回归
    文章目录​​0笔记说明​​​​1最小二乘法求线性回归模型​​​​2几何意义​​​​2.1平方损失函数的几何意义​​​​2.2用几何意义求线性回归模型​​​​3从概率......
  • 神秘算法 —— 线性基求交
    线性基求交:设\(A,B\)为两个线性基,\(V_A,V_B\)分别为其生成空间,则\(V_C=V_A\capV_B\)是一个线性空间,称\(A\)与\(B\)两个线性基的交为\(C\)。首先证明\(V_C\)......
  • Codeforces1778E 【线性基】【换根DP】
    我,差30秒,就能AC这道题,我知道错哪的时候是倒计时30,不记得优先级想赌一把,因为我没有先写括号的习惯,所以倒计时14的时候交了,然后想改了括号再交一发,改了括号之后,比赛结束了。......