2019年ACM-ICPC - 南昌网络赛H:The Nth Item【降幂+分块】

题目:

2019ICPC南昌网络赛H:The Nth Item

题意:

Fn = 3Fn-1 + 2Fn-2,Q次查询Fn (Q <= 1e7)

笔记:

注意到时限为1s,Q又大得可怕,直接矩阵快速幂是过不了的,但是数据有点弱,矩阵快速幂+记忆化也能跑过

广义斐波那契数列都能求出通项公式,方法参考:斐波那契数列通项公式是怎样推导出来的?

推出通项为:

Fn = \frac{1}{\sqrt{17}}(\frac{3+\sqrt{17}}{2})^n-\frac{1}{\sqrt{17}}(\frac{3-\sqrt{17}}{2})^n

分析:

(1)根号17在模P意义下等价于17模P的二次剩余,本题中这个值存在且是一个整数;但依然会多一个快速幂的log;这里n可以用欧拉降幂降到2e9以内,设N = \sqrt{2e9},再设 n = kN+rx^n = x^{kN+r}=x^{k^N}*x^r,注意到 k <= N,r <= N,预处理出最后的两部分就可以O(1)查询Fn

代码(266ms):

#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)查询

代码(246ms):

#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:二次剩余和广义斐波拉契循环节的模板均可以在本博客找到

你可能感兴趣的:(比赛----题解)