zoukankan      html  css  js  c++  java
  • Luogu P3873 [TJOI2010]天气预报 题解

    • 这题输入数据好坑啊。。

    本题解是给像我一样的蒟蒻写的,可能略显啰嗦,已经懂了的大佬可以出门右转,去切掉IOI

    • 我们先分析题目,将每一个(w_i),它的计算方式写成如下方式:
    • (w_i=a_1*w_{i-1}+a_2*w_{i-2}+cdots+a_n*w_{i-n})
    • 再注意到题中的这句话:

    a1, a2, ..., an是已知常数

    • 所以这是一个常系数的线性递推方程。很自然的,对于这种方程,我们可以想到直接(O(nm))递推(每个数都要乘n次)。
    • 继续看题。这时候我们惊讶的发现了下面这个东西:

    (1leq nleq 100,n < mleq 10000000)

    • 很好,跑满的时间复杂度为(O(10^9)),这绝对不会是这道题的正解不然怎么会是蓝题呀
    • 这怎么办呢?看起来好像没有什么优秀的办法来优化啊怎么可能,没有怎么做题

    这时候,就该我们神奇的矩阵快速幂(或称矩阵加速)出场了!

    矩阵快速幂

    • 前置知识:矩阵乘法快速幂
    • 数学姿势:
      • 1.矩阵满足结合律
      • 2.矩阵不满足交换律(这就要求了我们进行快速幂运算的时候必须要注意乘的顺序)
    • 由以上两点我们就可以得出一个很好用的结论:当一个矩阵乘上(k)次另一个矩阵的时候,我们可以看做是乘上这个矩阵的(k)次方,而矩阵可以像数一样用二进制的方式进行快速幂运算。
    • 由此我们就推出了矩阵快速幂的基本原理。

    我们先来举个栗子:

    • 斐波那契数列大家应该都很熟悉吧?它的递推式如下:(F_n=F_{i-1}+F_{i-2})
    • 在一定的范围以内,很显然可以(O(n))递推的,但是n很大呢?
    • 我们可以考虑矩阵快速幂。
    • 相信很多同学们都知道斐波那契数列的转移矩阵是这个形式的:
      (left[ egin{matrix} F_n\ F_{n-1}\ end{matrix} ight]=)(left[ egin{matrix} 1 & 1\ 1 & 0\ end{matrix} ight]^{n-2}*)(left[ egin{matrix} F_2\ F_1\ end{matrix} ight])
    • 但是为什么是这样的可能有些人却不太了解,这里给出解释。
    • 我们考虑两个矩阵相乘:
      (left[egin{matrix} a & b\ c & d\ end{matrix} ight]=)(left[egin{matrix} a_1 & b_1\ c_1 & d_1\ end{matrix} ight]*)(left[egin{matrix} a_2 & b_2\ c_2 & d_2\ end{matrix} ight])
    • 由矩阵乘法的定义我们可以知道:(a=a_1*a_2+b_1*c_2)
    • 我们将上方矩阵补齐为(2*2)的:

    (left[ egin{matrix} F_n & 0\ F_{n-1} & 0\ end{matrix} ight]=)(left[ egin{matrix} 1 & 1\ 1 & 0\ end{matrix} ight]^{n-2}*)(left[ egin{matrix} F_2 & 0\ F_1 & 0\ end{matrix} ight])

    • 那么当(n=3)时,我们有:
    • (F_3=1*F_2+1*F_1=2)
    • (F_2=1*F_2+0*F_1=1)
    • 继续向下推,我们惊奇的发现,对每一个新得到的表示(F_n)的矩阵,都是由这两个式子推过来的:
    • (F_n=1*F_{n-1}+1*F_{n-2})
    • (F_{n-1}=1*F_{n-1}+0*F_{n-2})
    • 我们发现它很神奇的符合了斐波那契数列的递推式,但是为什么?
    • 假设我们有形式这样一个等式:

    (left[egin{matrix} F_n\ F_{n-1}\ vdots \ F_{n-m+1}\ end{matrix} ight]=)(left[egin{matrix} a_1 & a_2 & cdots & a_m\ b_1 & b_2 & cdots & b_m\ vdots & vdots & vdots & vdots\ k_1 & k_2 & cdots & k_m\ end{matrix} ight]*)(left[egin{matrix} F_{n-1}\ F_{n-2}\ vdots \ F_{n-m}\ end{matrix} ight])

    • 我们进行类似的分析可以发现:对于每一个左边的目标矩阵中的(F_i),它都满足这样一个形式:((j)代表(F_i)这个数所对应的矩阵中的那一行)
    • (F_i=F_{n-1}*j_1+F_{n-2}*j_3+dots+F_{n-m}*j_m)
    • 因为斐波那契数列只和后两位有关,所以可以用一个(2*2)的矩阵来表示转移过程中的(bas)矩阵(中间那个),然后将(F_i)(F_{i-1})写成上面最左边的一列,而(F_{i-1})(F_{i-2})写成最右边的然后依次对应着将系数填入(bas),就得到了这个斐波那契数列的矩阵形式的转移方程。

    好了矩阵快速幂的介绍差不多就到这里,当然,听机房大佬说二维的好像也可以????这里就不管了因为我不会啊,我们来说一下这道题。

    • 我们看最开始给出的那个递推式,我们将它写成矩阵形式:
      (left[egin{matrix} W_i\ W_{i-1}\ W_{i-2}\ vdots \ W_{i-n+1}\ end{matrix} ight]=)(left[egin{matrix} a_1 & a_2 & a_3 & cdots & a_n\ 1 & 0 & 0 & cdots & 0\ 0 & 1 & 0 & cdots & 0\ vdots & vdots & vdots & vdots\ 0 & 0 & cdots & 1 & 0\ end{matrix} ight]*)(left[egin{matrix} W_{i-1}\ W_{i-2}\ W_{i-3}\ vdots \ W_{i-n}\ end{matrix} ight])
    • 因为我们要求第(m)天,所以我们需要在基础矩阵的基础上乘上(bas)矩阵的(m-n)次方(因为要推这么多次)。状态转移如下:
      (left[egin{matrix} W_m\ W_{m-1}\ W_{m-2}\ vdots \ W_{m-n+1}\ end{matrix} ight]=)(left[egin{matrix} a_1 & a_2 & a_3 & cdots & a_n\ 1 & 0 & 0 & cdots & 0\ 0 & 1 & 0 & cdots & 0\ vdots & vdots & vdots & vdots\ 0 & 0 & cdots & 1 & 0\ end{matrix} ight]^{m-n}*)(left[egin{matrix} W_n\ W_{n-1}\ W_{n-2}\ vdots \ W_1\ end{matrix} ight])
    • 准备工作已经做好了(DP的题基本也就一个推方程吧。。),剩下的就是记板子上代码了。

    AC Code:

    #include<bits/stdc++.h>
    #define del(a,i) memset(a,i,sizeof(a))
    #define ll long long
    #define inl inline
    #define il inl void
    #define it inl int
    #define ill inl ll
    #define re register
    #define ri re int
    #define rl re ll
    #define mid ((l+r)>>1)
    #define lowbit(x) (x&(-x))
    #define INF 0x3f3f3f3f
    #define mod 4147
    using namespace std;
    template<class T>il read(T &x)
    {
    	int f=1;char k=getchar();x=0;
    	for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
    	for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
    	x*=f;
    }
    const int MAXN = 105;
    int n,m,w[MAXN],a[MAXN];
    struct Matrix{    //矩阵快速幂模板
    	int val[MAXN][MAXN];
    	Matrix(){del(val,0);}
    	int *operator [](int x){return val[x];}
    	Matrix operator *(Matrix t){
    		Matrix res;
    		for(ri i=1;i<=n;i++)
    			for(ri j=1;j<=n;j++)
    				for(ri k=1;k<=n;k++)
    					res[i][j]=(res[i][j]+val[i][k]*t[k][j])%mod;
    		return res;
    	}
    }ans,bas;
    il init(){     //准备工作:构建矩阵
    	for(ri i=1;i<=n;i++) ans[i][1]=w[n-i+1];
    	for(ri i=1;i<=n;i++) bas[1][i]=a[i];
    	for(ri i=2;i<=n;i++) bas[i][i-1]=1;
    }
    it qpow(int w){   //形式和快速幂一毛一样
    	while(w){
    		if(w&1) ans=bas*ans;//这里要注意,就像前面说的,矩阵不具有交换性,一定是bas在前!!这一点很重要
    		bas=bas*bas;w>>=1;
    	}
    	return ans[1][1];
    }
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	read(n),read(m);
    	for(ri i=n;i;i--) read(w[i]);
    	for(ri i=1;i<=n;i++) read(a[i]);
    	init();
    	printf("%d
    ",qpow(m-n));
    	return 0;
    }
    
  • 相关阅读:
    day07_final
    day06_final
    day02_final
    day04_final
    New
    AtCoder Grand Contest 015 E Mr.Aoki Incubator
    长链剖分学习笔记
    关于某些莫队的优化
    CodePlus 2019 3月月赛 Div.1 A题 TREE
    边分治学习笔记
  • 原文地址:https://www.cnblogs.com/TheShadow/p/10861307.html
Copyright © 2011-2022 走看看