我是靠谱客的博主 暴躁火龙果,这篇文章主要介绍The Preliminary Contest for ICPC Asia Nanjing 2019 ICPC徐州站网络赛 H The Nth Item(二次剩余+分块打表或假算法杜教BM),现在分享给大家,希望可以做个参考。

 

题目链接: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次方差除以根的差,证明方法参见传送门

所以用上述方法,我们可以得到本题要求的斐波那契的通项公式为:f[n]=frac{(frac{3+sqrt{17}}{2})^{n}-(frac{3-sqrt{17}}{2})^{n}}{sqrt{17}}

看到根号,很多小伙伴就倒吸一口冷气,这玩个p啊!不要担心,可以使用二次剩余,得到模998244353意义下的根号17的代替数,就像逆元一样,二次剩余就是求x^{2}equiv n(mod p)

直接套板子,发现17的二次剩余是524399943

板子:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<bits/stdc++.h> 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<<solve(17)<<endl; }

可是到了现在,我们还是不能放下警惕!n是1e18量级的,1e7次查询如果套log直接凉凉,所以得把这个log给去掉,怎么才能去掉他呢?就到了下一个关键的地方,分块打表。

 

首先机智的小伙伴们都知道,a^{0}=1,又根据欧拉定理or费马小定理,a^{p-1}=1(mod p),所以p-1一定是a的幂次的循环节之一,那么最小循环节一定出现在它的因数中!所以我们首先要找到最小循环节,第一步得先知道刚才那两个家伙的值到底是多少,根号17都知道了,直接带进去搞一搞就行,

复制代码
1
2
int p1=(3+524399943)*powmod(2ll,MOD-2)%MOD; int p2=(3-524399943+MOD)*powmod(2ll,MOD-2)%MOD;

然后枚举所有因子找到最小循环节,分别是249561088和29360128

寻找最小循环节代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h> 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<<ans<<endl; ans=MOD-1,temp=MOD-1; rep(i,2,sqrt(temp)){ if(temp%i==0){ if(powmod(p2,i)==1){ ans=min(ans,i); } if(powmod(p2,temp/i)==1){ ans=min(ans,temp/i); } } } cout<<ans<<endl; }

终于到了最后的终极决战环节!这个最小循环节还是太大了,如何才能进一步缩小时间?

以249561088为例,打表预处理出1~100的情况,然后再预处理出100,200,300,400,500.....到249561000的情况,然后两边一凑就是答案了,时间复杂度却是这个数字的1/100,也就2e6,然后查询就能直接O(1)美滋滋

完整题目代码见下方

 

法二:递推式,讲道理直接自闭,赛场上一直tle,结束后看到牛B老哥说,直接把n标记一下,遇到出现过的n直接调用,否则就需要调用BM,就能过,原因不详

补充:此法应该是出题人没想到他的随机n并不随机存在循环节,导致能被摸鱼过,还是乖乖用法一吧。

以下是代码:

法一:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h> 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("%lldn",anss); } }

 

法二:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <vector> #include <string> #include <map> #include <set> #include <cassert> #include<bits/stdc++.h> #include<unordered_map> using namespace std; #define rep(i,a,n) for (int i=a;i<n;i++) #define per(i,a,n) for (int i=n-1;i>=a;i--) #define pb push_back #define mp make_pair #define all(x) (x).begin(),(x).end() #define fi first #define se second #define SZ(x) ((int)(x).size()) typedef vector<int> VI; typedef long long ll; typedef pair<int,int> PII; const ll mod=998244353; ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;} // head int _; ll n; namespace linear_seq { const int N=10010; ll res[N],base[N],_c[N],_md[N]; vector<int> Md; void mul(ll *a,ll *b,int k) { rep(i,0,k+k) _c[i]=0; rep(i,0,k) if (a[i]) rep(j,0,k) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod; for (int i=k+k-1;i>=k;i--) if (_c[i]) rep(j,0,SZ(Md)) _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod; rep(i,0,k) a[i]=_c[i]; } int solve(ll n,VI a,VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+... // printf("%dn",SZ(b)); ll ans=0,pnt=0; int k=SZ(a); assert(SZ(a)==SZ(b)); rep(i,0,k) _md[k-1-i]=-a[i];_md[k]=1; Md.clear(); rep(i,0,k) if (_md[i]!=0) Md.push_back(i); rep(i,0,k) res[i]=base[i]=0; res[0]=1; while ((1ll<<pnt)<=n) pnt++; for (int p=pnt;p>=0;p--) { mul(res,res,k); if ((n>>p)&1) { for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0; rep(j,0,SZ(Md)) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod; } } rep(i,0,k) ans=(ans+res[i]*b[i])%mod; if (ans<0) ans+=mod; return ans; } VI BM(VI s) { VI C(1,1),B(1,1); int L=0,m=1,b=1; rep(n,0,SZ(s)) { ll d=0; rep(i,0,L+1) d=(d+(ll)C[i]*s[n-i])%mod; if (d==0) ++m; else if (2*L<=n) { VI T=C; ll c=mod-d*powmod(b,mod-2)%mod; while (SZ(C)<SZ(B)+m) C.pb(0); rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod; L=n+1-L; B=T; b=d; m=1; } else { ll c=mod-d*powmod(b,mod-2)%mod; while (SZ(C)<SZ(B)+m) C.pb(0); rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod; ++m; } } return C; } int gao(VI a,ll n) { VI c=BM(a); c.erase(c.begin()); rep(i,0,SZ(c)) c[i]=(mod-c[i])%mod; return solve(n,c,VI(a.begin(),a.begin()+SZ(c))); } }; int f[100]; int q; unordered_map<ll,ll>mp1; int main() { vector<int>v; v.push_back(0); v.push_back(1); f[0]=0,f[1]=1; rep(i,2,4){ f[i]=3*f[i-1]+2*f[i-2]; v.push_back(f[i]); } while(~scanf("%d%lld",&q,&n)) { mp1.clear(); ll ans=0,anss=0; rep(i,1,q+1){ n=n^(ans*ans); if(mp1.count(n)){ ans=mp1[n]; anss^=ans; } else{ ans=linear_seq::gao(v,n); mp1[n]=ans; anss^=ans; } } printf("%lldn",anss); } }

 

最后

以上就是暴躁火龙果最近收集整理的关于The Preliminary Contest for ICPC Asia Nanjing 2019 ICPC徐州站网络赛 H The Nth Item(二次剩余+分块打表或假算法杜教BM)的全部内容,更多相关The内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(77)

评论列表共有 0 条评论

立即
投稿
返回
顶部