题目链接:https://nanti.jisuanke.com/t/41355
题目大意:f[0]=0,f[1]=1,f[n]=3*f[n-1]+2*f[n-2](n≥2),mod 998244353,一共有q轮,第一轮查的是f[n],接下来每一轮都用前一轮的答案的平方和前一轮查询的数异或作为这一轮的n进行查询,最后求所有答案的异或
题目思路:
法一:
法一使用分块打表,网上基本要么是法二的错误方法,要么对于法一很多关键数据的得出没有给出详细说明,所以菜鸡博主就准备在这篇题解中,将每一个数据的来源都分析清楚。
首先对于求解广义斐波那契数列f[i]=a*f[i-1]+b*f[i-2]的通项公式,可以使用q^2=a*q+b求得两个根,第i项就是两个根的i次方差除以根的差,证明方法参见传送门
所以用上述方法,我们可以得到本题要求的斐波那契的通项公式为:
看到根号,很多小伙伴就倒吸一口冷气,这玩个p啊!不要担心,可以使用二次剩余,得到模998244353意义下的根号17的代替数,就像逆元一样,二次剩余就是求
直接套板子,发现17的二次剩余是524399943
板子:
#include
using namespace std;
#define ll long long
ll w;
ll p=998244353;
struct Num{
ll x, y;
Num(){
x = y = 0;
}
Num(ll xx, ll yy){
x = xx;
y = yy;
}
Num operator * (Num const & a)const{
Num ans;
ans.x = ((x * a.x)%p + (y * a.y)%p * w %p + p)%p;
ans.y = ((x * a.y)%p + (y * a.x)%p + p)%p;
return ans;
}
};
ll pow_num(Num a, ll b){
Num ans = {1, 0};
while(b){
if(b&1)
ans = ans*a;
a = a*a;
b /= 2;
}
return ans.x%p;
}
ll pow(ll a, ll b){
ll ans = 1;
while(b){
if(b&1)
ans = ans*a%p;
a = a*a%p;
b /= 2;
}
return ans;
}
ll check(ll n){
ll ans = pow(n, (p-1)/2);
if(ans == p-1)
return -1;
else
return 1;
}
ll solve(ll n){
n %= p;
if(check(n) == -1)
return -1;
if(p == 2) return n;
ll a;
srand(time(NULL));
while(1){
a = rand()*13331%p;
w = ((a*a%p - n)%p + p)%p;
if(check(w) == -1)
break;
}
Num ans = {a, 1};
return pow_num(ans, (p+1)/2);
}
int main() {
cout<
可是到了现在,我们还是不能放下警惕!n是1e18量级的,1e7次查询如果套log直接凉凉,所以得把这个log给去掉,怎么才能去掉他呢?就到了下一个关键的地方,分块打表。
首先机智的小伙伴们都知道,,又根据欧拉定理or费马小定理,,所以p-1一定是a的幂次的循环节之一,那么最小循环节一定出现在它的因数中!所以我们首先要找到最小循环节,第一步得先知道刚才那两个家伙的值到底是多少,根号17都知道了,直接带进去搞一搞就行,
int p1=(3+524399943)*powmod(2ll,MOD-2)%MOD;
int p2=(3-524399943+MOD)*powmod(2ll,MOD-2)%MOD;
然后枚举所有因子找到最小循环节,分别是249561088和29360128
寻找最小循环节代码:
#include
using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int MOD = 998244353;
ll powmod(ll x,ll y){
ll rst=1;
for(;y;y>>=1){
if(y&1)rst=rst*x%MOD;
x=x*x%MOD;
}
return rst;
}
int main() {
int p1=(3+524399943)*powmod(2ll,MOD-2)%MOD;
int p2=(3-524399943+MOD)*powmod(2ll,MOD-2)%MOD;
int ans=MOD-1,temp=MOD-1;
rep(i,2,sqrt(temp)){
if(temp%i==0){
if(powmod(p1,i)==1){
ans=min(ans,i);
}
if(powmod(p1,temp/i)==1){
ans=min(ans,temp/i);
}
}
}
cout<
终于到了最后的终极决战环节!这个最小循环节还是太大了,如何才能进一步缩小时间?
以249561088为例,打表预处理出1~100的情况,然后再预处理出100,200,300,400,500.....到249561000的情况,然后两边一凑就是答案了,时间复杂度却是这个数字的1/100,也就2e6,然后查询就能直接O(1)美滋滋
完整题目代码见下方
法二:递推式,讲道理直接自闭,赛场上一直tle,结束后看到牛B老哥说,直接把n标记一下,遇到出现过的n直接调用,否则就需要调用BM,就能过,原因不详
以下是代码:
法一:
#include
using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int MAXN = 3e6;
const int MOD = 998244353;
int q;
ll n;
ll fa1[MAXN],fa100[MAXN],fb1[MAXN],fb100[MAXN];
ll powmod(ll x,ll y){
ll rst=1;
for(;y;y>>=1){
if(y&1)rst=rst*x%MOD;
x=x*x%MOD;
}
return rst;
}
int main() {
fa1[0]=fa100[0]=fb1[0]=fb100[0]=1;
rep(i,1,100){
fa1[i]=fa1[i-1]*262199973%MOD;
fb1[i]=fb1[i-1]*736044383%MOD;
}
rep(i,1,2495610){
fa100[i]=fa100[i-1]*fa1[100]%MOD;
}
rep(i,1,293601){
fb100[i]=fb100[i-1]*fb1[100]%MOD;
}
ll inv17=powmod(524399943,MOD-2)%MOD;
while(~scanf("%d%lld",&q,&n)){
ll ans=0,anss=0;
rep(i,1,q){
n=n^(ans*ans);
if(!n){ans=0;continue;}
int n1=n%249561088;
int n2=n%29360128;
ans=(fa1[n1%100]*fa100[n1/100]%MOD-fb1[n2%100]*fb100[n2/100]%MOD+MOD)%MOD*inv17%MOD;
anss^=ans;
}
printf("%lld\n",anss);
}
}
法二:
#include
#include
#include
#include
#include
#include
#include