2020牛客NOIP赛前集训提高第四场C-斐波(矩阵快速幂,数学)

题目链接

Problem Statement

2020牛客NOIP赛前集训提高第四场C-斐波(矩阵快速幂,数学)_第1张图片

Solution

  • 简洁解释一下题意,相信很多人都是卡在了题意理解上
  • 我们要求:
    • 题目所给 [ l , r ] [l,r] [l,r]区间
    • 的任意一个子区间
    • 中的数组成的集合
    • 的子集合中元素的和
    • 作为下标的斐波那契数
    • 的平方和
    • 之和
  • 所给 f f f含义是
  • f ( f( f(集合 ) ) )=所有子集的数字和作为下标的斐波那契数的平方和
  • 我们一层一层来做

Subtask 1:

区间的数字和作为下标的斐波那契数

  • 复习: f i b o n a c c i fibonacci fibonacci矩阵
  • 区间的数字和作为下标的斐波那契数

T = ∣ 1 1 1 0 ∣ \qquad\qquad\qquad\quad T=\begin{vmatrix}1&1\\1&0\end{vmatrix} T=1110

∴ f i b a + b = T a + b = T a ∗ T b = f i b a ∗ f i b b \therefore fib_{a+b}=T^{a+b}=T^a*T^b=fib_a*fib_b fiba+b=Ta+b=TaTb=fibafibb

Subtask 2:

区间的数字和作为下标的斐波那契数的平方

  • 这是一个常见套路,当然你也可以直接算,但也可以改一改矩阵

f i 2 = ( f i − 1 + f i − 2 ) 2 = f i − 1 2 + 2 f i − 1 f i − 2 + f i − 2 2 f_i^2=(f_{i-1}+f_{i-2})^2=f_{i-1}^2+2f_{i-1}f_{i-2}+f_{i-2}^2 fi2=(fi1+fi2)2=fi12+2fi1fi2+fi22

头尾两项好维护,关键是中间的二倍积

2 f i f i − 1 = 2 ( f i − 1 + f i − 2 ) f i − 1 = 2 f i − 1 2 + 2 f i − 1 f i − 2 2f_if_{i-1}=2(f_{i-1}+f_{i-2})f_{i-1}=2f_{i-1}^2+2f_{i-1}f_{i-2} 2fifi1=2(fi1+fi2)fi1=2fi12+2fi1fi2

∴ [ f i 2 f i − 1 2 2 f i f i − 1 ] = [ 1 1 2 1 0 0 1 0 1 ] ∗ [ f i − 1 2 f i − 2 2 2 f i − 1 f i − 2 ] \therefore \begin{bmatrix}f_i^2\\f_{i-1}^2\\2f_if_{i-1}\end{bmatrix}=\begin{bmatrix}1&1&2\\1&0&0\\1&0&1\end{bmatrix}*\begin{bmatrix}f_{i-1}^2\\f_{i-2}^2\\2f_{i-1}f_{i-2}\end{bmatrix} fi2fi122fifi1=111100201fi12fi222fi1fi2

Subtask 3:

集合的所有子集的数字作为下标的斐波那契数的平方和

容易发现 { { a } , { b } , { a , b } , ∅ } ⊆ { a , b } \{\{a\},\{b\},\{a,b\},\varnothing\}\subseteq\{a,b\} { { a},{ b},{ a,b},}{ a,b}

⟹ ( a + 1 ) ( b + 1 ) = a + b + a b + 1 \Longrightarrow(a+1)(b+1)=a+b+ab+1 (a+1)(b+1)=a+b+ab+1

注意矩阵的1是

I = [ 1 0 0 0 1 0 0 0 1 ] I=\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix} I=100010001

Subtask 4:full version

区间的子区间的集合的所有子集的数字和作为下标的斐波那契数的平方和的和

  • 区间的子区间问题
    2020牛客NOIP赛前集训提高第四场C-斐波(矩阵快速幂,数学)_第2张图片

  • m u l a \color{Red}mul_a mula记录区间 a a a内答案

  • m u l a = f i b a + I \color{Red}mul_a=fib_a+I mula=fiba+I

  • 易得 m u l a + b = m u l a ∗ m u l b \color{Red}mul_{a+b}=mul_a*mul_b mula+b=mulamulb

  • p r e a \color{SkyBlue}pre_a prea记录 a a a的前缀的答案

  • 则如图, p r e a + b = p r e a + m u l a ∗ p r e b \color{SkyBlue}pre_{a+b}=pre_a+mul_a*pre_b prea+b=prea+mulapreb

  • 同理,后缀答案 s u f a + b = s u f a ∗ m u l b + s u f b \color{RoyalBlue}suf_{a+b}=suf_a*mul_b+suf_b sufa+b=sufamulb+sufb

  • 所以总答案就是左边后缀拼右边前缀

  • a n s a + b = a n s a + a n s b + s u f a ∗ p r e b \color{LimeGreen}ans_{a+b}=ans_a+ans_b+suf_a*pre_b ansa+b=ansa+ansb+sufapreb

  • 这些我们用线段树维护,单点修改区间查询

Code

#include 
#include 
#define ls x<<1
#define rs x<<1|1
#define ll long long
const int N=1e5+10,M=3,mod=998244353;
int n,q,val[N];
inline ll add(ll a){
     return a>mod?a-mod:a;}
struct Matrix{
     ll a[M][M];};
Matrix one,fib[N];
inline Matrix operator+(const Matrix &x,const Matrix &y)
{
     
    Matrix ret;
    memset(ret.a,0,sizeof(ret.a));
    for(int i=0;i<M;i++)
        for(int j=0;j<M;j++)
            ret.a[i][j]=add(x.a[i][j]+y.a[i][j]);
    return ret;
}
inline Matrix operator*(const Matrix &x,const Matrix &y)
{
     
    Matrix ret;
    memset(ret.a,0,sizeof(ret.a));
    for(int i=0;i<M;i++)
        for(int k=0;k<M;k++)
            if(x.a[i][k])
                for(int j=0;j<M;j++)
                    ret.a[i][j]=add(ret.a[i][j]+x.a[i][k]*y.a[k][j]%mod);
    return ret;                
}
inline Matrix operator^(Matrix x,ll y)
{
     
    Matrix ret=one;
    while(y){
     if(y&1)ret=ret*x;x=x*x,y>>=1;}
    return ret;
}
struct Segment
{
     
	int l,r;
	Matrix mul,pre,suf,ans;
};
Segment T[N<<2];
inline Segment merge(const Segment &a,const Segment &b)
{
     
	Segment ret;
	ret.mul=a.mul*b.mul;
	ret.pre=a.pre+a.mul*b.pre;
	ret.suf=b.suf+b.mul*a.suf;
	ret.ans=a.ans+b.ans+a.suf*b.pre;
	return ret;
}
inline void build(int x,int l,int r)
{
     
    T[x].l=l,T[x].r=r;
    if(l==r){
     T[x].ans=T[x].mul=T[x].pre=T[x].suf=fib[val[l]];return ;}
    int m=l+r>>1;
    build(ls,l,m),build(rs,m+1,r);
	Segment s=T[x];
	T[x]=merge(T[ls],T[rs]);
	T[x].l=s.l,T[x].r=s.r;
}
inline void modify(int x,int k,int p)
{
     
    if(T[x].l==k&&T[x].r==k){
     T[x].ans=T[x].mul=T[x].pre=T[x].suf=fib[p];return ;}
    int mid=T[x].l+T[x].r>>1;
    if(k<=mid) modify(ls,k,p);
    else modify(rs,k,p);
    Segment s=T[x];
	T[x]=merge(T[ls],T[rs]);
	T[x].l=s.l,T[x].r=s.r;
}
inline Segment query(int x,int l,int r)
{
     
    if(T[x].l==l&&T[x].r==r) return T[x];
    int mid=T[x].l+T[x].r>>1;
    if(r<=mid) return query(ls,l,r);
    else if(l>mid) return query(rs,l,r);
    return merge(query(ls,l,mid),query(rs,mid+1,r));
}
int main()
{
     
    for(int i=0;i<M;i++) one.a[i][i]=1;
    Matrix m1;
    m1.a[0][0]=1;m1.a[0][1]=1;m1.a[0][2]=2;
    m1.a[1][0]=1;m1.a[1][1]=0;m1.a[1][2]=0;
    m1.a[2][0]=1;m1.a[2][1]=0;m1.a[2][2]=1;
    fib[0]=one;
    for(int i=1;i<N;i++) fib[i]=fib[i-1]*m1;
    for(int i=1;i<N;i++) fib[i]=fib[i]+one; 
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",val+i);
    build(1,1,n);
    while(q--)
    {
     
    	int op,x,y;
    	scanf("%d%d%d",&op,&x,&y);
        if(op==1) modify(1,x,y);
        else printf("%lld\n",query(1,x,y).ans.a[1][0]);
    }
}

你可能感兴趣的:(2020牛客暑期多校训练营,矩阵,数学)