题目链接: L O J # 10041 \rm LOJ~\#10041 LOJ #10041
这道题有三种解法,一本通的编排为我们提示了其中唯一一种会被卡的解法 (mmp)
1. 哈希表 \large \textbf{1. 哈希表} 1. 哈希表
正是万恶的一本通给我们指引的一条道路
非常的暴力,往哈希表里不断加数即可,每算出一项就查一次表。
时间复杂度约
Θ
(
2000000
)
\Theta(2000000)
Θ(2000000) ,看上去毫无问题
于是这里有一份代码:
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int P=100003; int a,b,c,x=1,s,last[P];
struct node { int x,pre; }; vector<node>f;
//这里用vector是想优化一下空间,事实上数组即可
int find() {
for (int i=last[s]; ~i; i=f[i].pre)
if (f[i].x==x) return 1;
return 0;
}
int main() {
scanf("%d%d%d",&a,&b,&c);
memset(last,-1,sizeof last);
f.push_back({1,-1}); last[1]=0;
for (int i=1; i<=2000000; ++i) {
x=(1ll*a*x+x%b)%c; s=x%P;
if (find()) return printf("%d\n",i),0;
f.push_back({x,last[s]}); last[s]=f.size()-1;
}
puts("-1"); return 0;
}
在
L
O
J
\rm LOJ
LOJ 上是可以过的。你把模数调到 1145141
还会跑得更快。
不过时间不是重点,这份代码被光荣地卡空间了。。
30 % 30\% 30% 的数据 空间限制 4 M \rm 4M 4M。 ( 囿 于 测 评 系 统 的 实 现 , 这 部 分 的 空 间 限 制 取 消 ( 小 声 \tiny(囿于测评系统的实现,这部分的空间限制取消(小声 (囿于测评系统的实现,这部分的空间限制取消(小声
只要数据稍微强一些,就会导致我们存的数大大增多,然后就算 vector
也挽救不了了
这种做法是无法完美通过这道题的。我们需要再研究一下别的做法。
2. 双哈希 \large \textbf{2. 双哈希} 2. 双哈希
去
L
O
J
\rm LOJ
LOJ 翻了一下提交记录,然后发现有人用这种做法在
4
M
B
\rm 4MB
4MB 的空间限制内通过了此题。
提交记录
单哈希做法很好想,但是冲突概率极大以至于无法通过此题,所以考虑使用双哈希。
双哈希一般是不会被卡掉的,直接认为是正确的就好了
不过这种做法空间是线性的,仍然需要占用 2 M B + \rm 2MB+ 2MB+ 的空间,事实上我们有更优的解法。
3. 数学做法 \large \textbf{3. 数学做法} 3. 数学做法
观察数列的递推式可以看出,这个数列是存在循环节的。
也就是说,整个数列大概是下面这种情况:
根据题目要求,循环节长度不能超过
2
×
1
0
6
2\times10^6
2×106 ,所以
a
2
×
1
0
6
a_{2\times10^6}
a2×106 一定在这个循环里面。
于是我们找到第一个和
a
2
×
1
0
6
a_{2\times10^6}
a2×106 相等的元素,它们之间的距离就是循环节的长度
l
e
n
\rm len
len 。如果没有找到则说明循环节长度大于
2
×
1
0
6
2\times10^6
2×106 ,此时输出
−
1
-1
−1 。
于是我们可以分别从 a 0 a_0 a0 和 a l e n a_{len} alen 开始同时往后计算,找到的第一对相等元素即是答案。
代码:
#include<cstdio>
const int inf=2e6; int a,b,c,s1=1,s2=1,len;
int main() {
scanf("%d%d%d",&a,&b,&c);
for (int i=1; i<=inf; ++i) s1=(1ll*s1*a+s1%b)%c; //计算a[inf]
s1==1&&(len=inf); //首先判断a[0]是否与a[inf]相等
for (int i=1; i<inf; ++i) { //注意这里不能取等号
s2=(1ll*s2*a+s2%b)%c;
s1==s2&&(len=inf-i); //记录两元素的距离
}
//循环结束后,len更新至最小值,即是循环节长度
if (!len) return puts("-1"),0; //没有更新len,说明循环节长度大于inf
s1=s2=1;
for (int i=1; i<=len; ++i) s2=(1ll*s2*a+s2%b)%c; //计算a[len]
for (int i=0; i<=inf; ++i) { //注意要从0开始
if (s1==s2) return printf("%d\n",i+len),0; //相等时输出答案
s1=(1ll*s1*a+s1%b)%c; s2=(1ll*s2*a+s2%b)%c; //同时向后递推
}
puts("-1"); //没有找到解
return 0;
}