2019ICPC南昌网络赛H:The Nth Item
Fn = 3Fn-1 + 2Fn-2,Q次查询Fn (Q <= 1e7)
注意到时限为1s,Q又大得可怕,直接矩阵快速幂是过不了的,但是数据有点弱,矩阵快速幂+记忆化也能跑过
广义斐波那契数列都能求出通项公式,方法参考:斐波那契数列通项公式是怎样推导出来的?
推出通项为:
(1)根号17在模P意义下等价于17模P的二次剩余,本题中这个值存在且是一个整数;但依然会多一个快速幂的log;这里n可以用欧拉降幂降到2e9以内,设,再设 ,,注意到 k <= N,r <= N,预处理出最后的两部分就可以O(1)查询Fn
#include
using namespace std;
typedef long long LL;
const int N = 45000;
const LL p = 998244353;
const LL phi = 998244352;
const LL inv2 = 499122177;
const LL v17 = 524399943; //17模P的二次剩余
const LL inv17 = 559329360; //v17的逆元
const LL a = 262199973; //(3+sqrt(17))/2
const LL b = 736044383; //(3-sqrt(17))/2
//const int v17 = 473844410; //17模P的另一个二次剩余
LL qpow(LL a,LL x,LL mod){
LL res = 1;
while(x){
if(x&1) res = res*a % mod;
a = a*a % mod;
x >>= 1;
}
return res;
}
LL F[N+10],F1[N+10],f[N+10],f1[N+10],n;
void init(){
F[0] = f[0] = F1[0] = f1[0] = 1;
for(int i = 1;i <= N; ++i) F[i] = qpow(a,i,p),f[i] = qpow(b,i,p);
for(int i = 1;i <= N; ++i) F1[i] = qpow(F[i],N,p),f1[i] = qpow(f[i],N,p);
}
inline LL Find(LL x){
if(x >= phi) x = x%phi+phi;
return (((F1[x/N]*F[x%N])-(f1[x/N]*f[x%N]))%p+p)*inv17%p;
}
int main(){
init(); int q; cin >> q >> n;
LL ans = Find(n); LL res = ans;
for(int i = 2;i <= q; ++i){
n = n^(ans*ans);
ans = Find(n);
res ^= ans;
}
cout << res << '\n';
return 0;
}
(2)如果17模P的二次剩余不存在,那么可以找到广义斐波那契数列的循环节,同样将n的值大幅减小,然后用同样的方式预处理矩阵的幂次,也可以O(1)查询
#include
#define sz(x) (x).size()
using namespace std;
const int N = 32000;
const int k = 998244352; //Fn的循环节
const int mod = 998244353;
typedef long long ll;
struct matrix{
ll m[2][2];
};
matrix I = {1,0,0,1};
inline matrix operator * (const matrix &A,const matrix &B){
matrix res;
res.m[0][0] = (A.m[0][0]*B.m[0][0]+A.m[0][1]*B.m[1][0])%mod;
res.m[0][1] = (A.m[0][0]*B.m[0][1]+A.m[0][1]*B.m[1][1])%mod;
res.m[1][0] = (A.m[1][0]*B.m[0][0]+A.m[1][1]*B.m[1][0])%mod;
res.m[1][1] = (A.m[1][0]*B.m[0][1]+A.m[1][1]*B.m[1][1])%mod;
return res;
}
matrix qpow(matrix A,int x){
matrix res = I;
while(x){
if(x&1) res = A*res;
A = A*A;
x >>= 1;
}
return res;
}
matrix F[N+10],f[N+10];
void init(){
F[0] = f[0] = I; matrix A = {3,2,1,0};
for(int i = 1;i <= N; ++i) f[i] = qpow(A,i);
for(int i = 1;i <= N; ++i) F[i] = qpow(f[i],N);
}
inline ll Find(ll x){
if(x >= k) x %= k;
if(x == 0) return 0;
if(x == 1) return 1;
matrix B = {1,0,0,0};
matrix t = F[(x-1)/N]*f[(x-1)%N];
B = B * t;
return B.m[0][0];
}
int main(){
init();ll q,n; cin >> q >> n;
ll ans = Find(n); ll res = ans;
for(int i = 2;i <= q; ++i){
n = n^(ans*ans);
ans = Find(n);
res ^= ans;
}
cout << res << '\n';
return 0;
}
(3)分段打表的方法
ps:二次剩余和广义斐波拉契循环节的模板均可以在本博客找到