「一本通 2.1 练习 7」门票

题目链接: 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值