1. 程式人生 > 實用技巧 >【CF575A】Fibonotci(矩乘套路題)

【CF575A】Fibonotci(矩乘套路題)

點此看題面

  • 給定一個長度為\(n\)的陣列\(s\),從\(0\)開始編號。
  • 規定當\(i\ge n\)時,除去\(m\)個直接給定的\(s_i\),其餘\(s_i\)一律等於\(s_{i\ mod\ n}\)
  • 已知\(f_0=0,f_1=1,\forall i\ge2,f_i=s_{i-2}f_{i-2}+s_{i-1}f_{i-1}\),求\(s_k\)
  • \(n,m\le5\times10^4,k\le10^{18}\)

\(m=0\)時直接上矩乘

假設\(m=0\),則第\(i\)個位置的轉移矩陣就是:

\[\begin{bmatrix} 0&s_{(i-2)\ mod\ n}\\ 1&s_{(i-1)\ mod\ n} \end{bmatrix} \]

那麼就是要求第\(2\sim k\)個矩陣的總乘積,顯然發現它的週期為\(n\),這種東西隨便做做就好了,相信大家都會,畢竟並不是此題的核心所在。

取關鍵點

考慮一個給定的\(s_i\)影響到的是\(s_{i+1}\)\(s_{i+2}\),因此我們可以找出\(2m\)個關鍵點。

兩個關鍵點之間的轉移可以按照上面的方式搞,而關鍵點直接特殊造一下矩陣就好了。

這裡可能會涉及求出一段區間矩陣的乘積,講道理矩陣的乘積應該是不能用字首積差分得到的,想想反正已經有一個\(log\)了,乾脆寫個線段樹求一下矩陣區間乘積就好了。

程式碼:\(O(mlogk)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define LL long long
#define S(x) (U.count(x)?U[x]:s[(x)%n])//求出實際的s[x],也就是判斷其是否為m個特殊點之一
using namespace std;
int n,m,X,s[N+5];LL k,q[2*N+5];map<LL,int> U;
struct M
{
	int a[2][2];I M(CI A=0,CI B=0,CI C=0,CI D=0) {a[0][0]=A,a[0][1]=B,a[1][0]=C,a[1][1]=D;}
	I int* operator [] (CI x) {return a[x];}I Con int* operator [] (CI x) Con {return a[x];}
	I M operator * (Con M& o) Con//矩陣乘法
	{
		M t;t[0][0]=(1LL*a[0][0]*o[0][0]+1LL*a[0][1]*o[1][0])%X;
		t[0][1]=(1LL*a[0][0]*o[0][1]+1LL*a[0][1]*o[1][1])%X;
		t[1][0]=(1LL*a[1][0]*o[0][0]+1LL*a[1][1]*o[1][0])%X;
		t[1][1]=(1LL*a[1][0]*o[0][1]+1LL*a[1][1]*o[1][1])%X;return t;
	}
	I M operator ^ (LL y) Con//矩陣快速冪
	{
		M x=*this,t(1,0,0,1);W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;
	}
};
class SegmentTree
{
	private:
		#define PT CI l=0,CI r=n-1,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		M V[N<<2];
	public:
		I void Build(PT)//建樹
		{
			if(l==r) return (void)(V[rt]=M(0,s[(l-2+n)%n],1,s[(l-1+n)%n]));
			RI mid=l+r>>1;Build(LT),Build(RT),V[rt]=V[rt<<1]*V[rt<<1|1];
		}
		I M Q(CI L,CI R,PT)//求矩陣區間乘積
		{
			if(L>R) return M(1,0,0,1);if(L==l&&r==R) return V[rt];RI mid=l+r>>1;
			if(R<=mid) return Q(L,R,LT);if(L>mid) return Q(L,R,RT);return Q(L,mid,LT)*Q(mid+1,R,RT);
		}
}S;
int main()
{
	RI i;if(scanf("%lld%d",&k,&X),!k) return puts("0"),0;if(k==1) return puts(X^1?"1":"0"),0;//特判k=0或1
	for(scanf("%d",&n),i=0;i^n;++i) scanf("%d",s+i);S.Build(0,n-1);//讀入矩陣預處理
	LL x,y;for(scanf("%d",&m),i=1;i<=m;++i) scanf("%lld%lld",&x,&y),U[x]=y,q[i]=x+1,q[m+i]=x+2;//記下2m個關鍵點
	q[2*m+1]=k,sort(q+1,q+2*m+2),m=unique(q+1,q+2*m+2)-q-1;W(m&&q[m]>k) --m;//強制加入k成為最後一個關鍵點
	LL nx,ny;M t;t[1][1]=1;for(x=0,y=1,i=1;i<=m;x=nx,y=ny,++i)
		nx=q[i]/n,ny=q[i]%n,y>=ny&&(++x,t=t*S.Q(y+1,n-1)*S.Q(0,ny-1),y=ny-1),//強制y≤ny-1
		t=t*((S.Q(y+1,n-1)*S.Q(0,y))^(nx-x))*S.Q(y+1,ny-1)*M(0,S(q[i]-2),1,S(q[i]-1));//中間長度為n的週期直接快速冪一起跳,最後特殊構造關鍵點的矩陣
	return printf("%d\n",t[1][1]),0;//輸出答案
}