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;
    }
    
  • 相关阅读:
    使用 ASP.NET Core MVC 创建 Web API(五)
    使用 ASP.NET Core MVC 创建 Web API(四)
    使用 ASP.NET Core MVC 创建 Web API(三)
    使用 ASP.NET Core MVC 创建 Web API(二)
    使用 ASP.NET Core MVC 创建 Web API(一)
    学习ASP.NET Core Razor 编程系列十九——分页
    学习ASP.NET Core Razor 编程系列十八——并发解决方案
    一个屌丝程序猿的人生(九十八)
    一个屌丝程序猿的人生(九十七)
    一个屌丝程序猿的人生(九十五)
  • 原文地址:https://www.cnblogs.com/TheShadow/p/10861307.html
Copyright © 2011-2022 走看看