2019南昌網絡賽 H. The Nth Item(廣義斐波那契數列求通項公式模板)(二次剩餘+分塊)

鏈接:https://nanti.jisuanke.com/t/41355

題意:

Q個詢問,每次求F(N),但是N要用上一次詢問的結果得到。

思路:

1、直接矩陣快速冪求,再用map記一下答案,求過就不求了。數據正常的話肯定就會T,但這題數據太水。(也可能是詢問加密的問題,反正理論上鐵定T。)

#include<iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include<map>
#include<unordered_map>
#include<cmath>
#define ll long long
const int maxn=1e7+10;
using namespace std;
const ll mod = 998244353;
unordered_map<ll,ll>mp;
struct mat
{
	ll m[2][2];
}a,b;
int q;
ll ans[maxn],n,nn,res;
mat mul(mat a,mat b)
{
	mat c;
	c.m[0][0]=c.m[0][1]=c.m[1][0]=c.m[1][1]=0;
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				c.m[i][j]=(c.m[i][j]+a.m[i][k]*b.m[k][j]%mod)%mod;
	return c;
}
mat poww(mat a,ll b)
{
	mat res;
	res.m[0][0]=res.m[1][1]=1;
	res.m[0][1]=res.m[1][0]=0;
	while(b)
	{
		if(b&1) res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%lld",&q,&n);
	a.m[0][0]=3,a.m[0][1]=2;
	a.m[1][0]=1,a.m[1][1]=0;
	int pos=0;
	int i=0;
	ll sum=0;
	ans[0]=0;
	for(i=1;i<=q;i++)
	{
		if(i!=1) n^=(ans[i-1]*ans[i-1]);
		nn=n;
		if(mp.count(nn))
        {
            ans[i]=mp[nn];
            sum^=ans[i];
        }
		else
		{
			b=poww(a,nn-1);
			res=b.m[0][0];
			ans[i]=res;
			mp[nn]=res;
			sum^=ans[i];
		}
	}
	printf("%lld\n",sum);
    return 0;
}

 思路2(正解):首先,這是一個廣義斐波那契數列,也是一個常係數線性遞推數列。常係數線性遞推數列的通項公式,有很多方法,我參考了百度求斐波那契數列通項公式的步驟,總結了一下求廣義斐波那契數列通項公式的套路。

常係數線性遞推數列的通項公式的一般形式爲:

A(n)=d1*x1^n+d2*x2^n+...+dk*xk^n

 F(n)=(r+s)*F(n-1)-r*s*F(n-2)

現有廣義斐波那契數列F(n)=C1*F(n-1)+C2*F(n-2)

可得出方程組:r+s=C1\\ r*s=-C2

聯立得:r*r-C1*r-C2=0

解得:r=\frac{C1+\sqrt(C1*C1+4*C2)}{2}r=\frac{C1-\sqrt(C1*C1+4*C2)}{2}

代入上式得:s=\frac{C1-\sqrt(C1*C1+4*C2)}{2}s=\frac{C1+\sqrt(C1*C1+4*C2)}{2}

 

廣義斐波那契數列通項公式的一般形式爲:F(n)=k1*r^n+k2*s^n

將數列的前兩項代入即可解出k1,k2。套路結束。

對於本題:

C1=3,C2=2,F(1)=1,F(2)=3。通項公式便爲:F(n)=1/sqrt(17)*[((3+sqrt(17))/2)^n-((3-sqrt(17))/2)^n]。

得到公式我們看到有根號,這可咋辦,再怎麼高精度也不行啊。這裏就要用到二次剩餘(就是求一個數開根號後對模數取模的等價數。)(至於怎麼求,鑫爺會。嘿嘿。)例如本題,就是要求sqrt(17)在模998244353時的等價數,也就是17的二次剩餘。假設二次剩餘爲x,那麼公式裏的全部sqrt(17)都可以換爲x。這樣我們可以用快速冪logn的求一次詢問,這樣求複雜度和矩陣快速冪是同一量級的只不過常數小一些。

對於一個數x,求(x^n)%p,顯然可以用歐拉定理降冪爲x^(n%phi(p)+phi(p)),本題p=998244353,那麼指數n最大便爲:1996488703。我們可以分塊預處理,設N=sqrt(1996488703),把X[0]=x^0,X[1]=x^1,...,X[n]=x^N預處理出來,再把pre[0]=x^(0*N),pre[1]=x^(1*N),...,pre[N]=x^(N*N))。

求出來,這樣對於一個詢問n=a*N+b,我們其實就是求 (x^(a*N))*(x^b),即pre[a]*X[b]=pre[n/N]*X[n%N]。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e4;
const ll mod = 998244353;
const ll inv17 = 438914993;//sqrt(17)的逆元 
const ll x1 = 736044383;//(3+sqrt(17))/2
const ll x2 = 262199973;//(3-sqrt(17))/2
ll X1[N+10],X2[N+10],pre1[N+10],pre2[N+10];
ll ans,n,q,nn,res;
ll mo(ll x){ return x>=mod?x%mod:x; }
void Init()
{
	pre1[0]=pre2[0]=X1[0]=X2[0]=1;
	for(int i=1;i<=N;i++)
		X1[i]=mo(X1[i-1]*x1),X2[i]=mo(X2[i-1]*x2);		
	for(int i=1;i<=N;i++)
		pre1[i]=mo(pre1[i-1]*X1[N]),pre2[i]=mo(pre2[i-1]*X2[N]);			
}
int main(void)
{
	Init();
	scanf("%lld%lld",&q,&n);
	res=ans=0;
	while(q--)
	{
		n^=(res*res);
		nn=n%(mod-1)+mod-1; 
		res=mo(pre1[nn/N]*X1[nn%N])-mo(pre2[nn/N]*X2[nn%N]);
		res=mo(res+mod);
		res=mo(res*inv17);
		ans^=res;
		
	}
	printf("%lld\n",ans);
	
	return 0;	
} 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章