2019南昌网络赛 H. The Nth Item(广义斐波那契数列求通项公式模板)(二次剩余+分块)

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

题意:

2019南昌网络赛 H. The Nth Item(广义斐波那契数列求通项公式模板)(二次剩余+分块)_第1张图片

2019南昌网络赛 H. The Nth Item(广义斐波那契数列求通项公式模板)(二次剩余+分块)_第2张图片

Q个询问,每次求F(N),但是N要用上一次询问的结果得到。

思路:

1、直接矩阵快速幂求,再用map记一下答案,求过就不求了。数据正常的话肯定就会T,但这题数据太水。(也可能是询问加密的问题,反正理论上铁定T。)

#include
#include 
#include 
#include 
#include
#include
#include
#define ll long long
const int maxn=1e7+10;
using namespace std;
const ll mod = 998244353;
unordered_mapmp;
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

2019南昌网络赛 H. The Nth Item(广义斐波那契数列求通项公式模板)(二次剩余+分块)_第3张图片

 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 
#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;	
} 

 

你可能感兴趣的:(=====模板=====,=====数论=====)