[loj3464] [luogu7325] [WC2021] 斐波那契 - 斐波那契数列 - 递推 - 数论 - 循环节 - 扩展欧几里得(exGCD) - 扩展中国剩余定理(exCRT)

题目链接:https://www.luogu.com.cn/problem/P7325

题目大意:定义如下“广义斐波那契数列”:f_0=a,\ f_1=b,\ f_i=f_{i-1}+f_{i-2}(i\geq 2)

共有n组询问,每组给出ab,对于某个固定的模数m,求最小的p,使得f_p\equiv 0(\mod m)

观察数据范围大概能猜出几点:

1、m是固定的,因此很可能要预处理一堆东西来实现单组快速查询;

2、有一堆形如“m是质数”“m不含平方因子”之类的部分分,这在数论题中是很强的暗示——很有可能需要对每个质因子处理之后CRH合并,同时处理质数幂可能比处理质数复杂一些。

斐波那契数列有一些很休闲的性质,比如我们通常会把它看作一个线性递推:从(a,b)递推到(b,a+b)

我们盯着二元组(x,y)看,在模意义下这样的二元组数量是有限的,同时上述线性递推显然可逆,因此一定会形成纯循环。

(a,b)出发,我们的目标是推到某个(x,0),如果(a,b)所在的循环节里有形如(x,0)的元素就行了,否则无解。

当然可以很trivial地把所有的循环节都找出来,并对于每个二元组记录一下它所在的循环节大小和到下一个(x,0)的距离,然后直接查询就行。

只可惜二元组的数量是O(m^2)级别,因此还需要想办法。

等一下,不如我们先看看m是质数时会发生什么?

这时候就需要一些想象力了:m是质数有什么性质?额……比如模意义下所有非零元都与m互质?可以在模意义下随便作除法?

假设我们有一组询问是(a,b),那么把它变成(ka,kb),其中k\not\equiv 0(\mod m),会发生什么事?会对答案有影响吗?

嗯……好像没有影响啊。

这是因为,同乘k之后会让数列里所有项都乘k,而如果km互质,容易证明kx\equiv 0(\mod m)当且仅当x\equiv 0(\mod m)

所以,如果我们面对一个(a,b),其中a非零(是零的话就不用算了),那就可以把它在模意义下同除以a,变成(1,\frac{b}{a} )

我们惊奇地发现,原先O(m^2)级别的二元组数量被我们一下子砍成了O(m)

需要注意,不光起始状态可以这么操作,递推过程中的状态也可以,于是我们就对着这O(m)个元素处理循环节就行了。

下一步考虑m不含平方因子的情况:对着每个质因子求一个答案,但是求出的答案当然不是唯一的,毕竟沿着循环节再多走几圈也行。

如果对于当前质因子,初始状态所在循环节大小为x,到达第一个0的距离是y,那么最终答案只要满足kx+y(k\in N)的形式就行。

这是什么?同余方程啊!所以把每个质因子求的结果exCRT一下就行了。

最后来考虑一下p^c的情况:稍微麻烦之处在于不一定能作除法了。

不过问题不大,因为所有状态一定是形如(a*p^i,b)的形式,其中ap互质,0\leq i < c

虽然p^i的部分除不掉,但是a的部分还是能除的(需要一个exGCD),因此我们把所有二元组都化成(p^i,x)的形式就行了,这是O(m \log m)级别的。

需要注意三点:

一是容易证明两个(p^i,0)(p^j,0)一定不在同一个循环节里,因此最后求出的一定还是一个形如kx+y(k\in N)的同余方程;

二是在这时压缩状态后的循环就不是纯循环了,而是\rho形,需要在实现时特别注意;

三是需要特殊处理一下形如(0,x)的状态。

总复杂度O(m \log^2 m)

代码很丑而且细节很多:

#include<bits/stdc++.h>
#define gc getchar()
#define pc putchar
#define li long long
using namespace std;
inline li read(){
	li x = 0;
	int y = 0,c = gc;
	while(c < '0' || c > '9') y = c,c = gc;
	while(c >= '0' && c <= '9') x = x * 10 + c - '0',c = gc;
	return y == '-' ? -x : x;
}
inline void prt(li x){
	if(x >= 10) prt(x / 10);
	pc(x % 10 + '0');
}
inline void print(li x){
	if(x < 0) pc('-'),x = -x;
	prt(x);
}
int n,m,a[100010],b[100010],cs[100010],cc[17];
int pi[15],ci[15],cnt;
int to[17][100010][2];
li an[17][100010][2],as[100010][3],mo;
inline li gcd(li x,li y){
	return !y ? x : gcd(y,x % y);
}
inline li exgcd(li a,li b,li &x,li &y){
	if(!b){
		x = 1;y = 0;
		return a;
	}
	li g = exgcd(b,a % b,x,y);
	li t = x;x = y;y = t - a / b * y;
	return g;
}
inline li lcm(li x,li y){
	return x / gcd(x,y) * y;
}
void crt(li &p1,li &p2,li q1,li q2){
	if(p2 == -2 || q2 == -2){
		p2 = -2;return;
	}
	if(q1 == 1) return;
	if(p1 == 1){
		p1 = q1;p2 = q2;return;
	}
	li x,y,tmp = (p2 - q2 % p1 + p1) % p1,t2 = gcd(tmp,q1);
	tmp /= t2;q1 /= t2;
	if(exgcd(q1,p1,x,y) != 1){
		p2 = -2;return;
	}
	if(x < 0) x += p1;
	q1 *= t2;
	li k = tmp * x % p1;
	p1 = lcm(p1,q1);
	p2 = (k * q1 + q2) % p1; 
}
inline void nxt(int &a,int &b){
	li u = b,v = (cc[a] + b) % mo,x,y;
	int k = exgcd(u,mo,x,y),l = cs[k];
	if(x < 0) x += mo / k;
	(v *= x) %= mo;
	a = l;b = v;
}
void dfs(int x,int y,int st){
	if(!y){
		an[x][y][0] = st + 1;
		an[x][y][1] = 0;
		return;
	}
	int tx = x,ty = y;
	nxt(tx,ty);
	dfs(tx,ty,st + 1);
	an[x][y][0] = an[tx][ty][0];
	an[x][y][1] = an[tx][ty][1] + 1;
}
void dfs2(int x,int y){
	if(an[x][y][0] > 0) return; 
	if(an[x][y][0] == -1){
		an[x][y][1] = -2;
		return;
	}
	an[x][y][0] = -1;
	int tx = x,ty = y;
	nxt(tx,ty);
	dfs2(tx,ty);
	an[x][y][0] = an[tx][ty][0];
	an[x][y][1] = an[tx][ty][1] + 1;
	if(an[x][y][1] == -1) --an[x][y][1];
}
int main(){
	int i,j,k,l;
	li x,y;
	n = read();m = read();
	for(i = 1;i <= n;++i) a[i] = read() % m,b[i] = read() % m,as[i][0] = 1;
	if(m == 1){
		for(i = 1;i <= n;++i) pc('0'),pc('\n');
		return 0;
	}
	int tmp = m;
	for(i = 2;i * i <= tmp;++i) if(tmp % i == 0){
		pi[++cnt] = i;
		while(tmp % i == 0) ++ci[cnt],tmp /= i;
	}
	if(tmp != 1) pi[++cnt] = tmp,ci[cnt] = 1;
	int tx,ty;
	for(i = 1;i <= cnt;++i){
		int p = pi[i],c = ci[i];
		cc[0] = 1;
		for(j = 1,k = p;j <= ci[i];++j,k *= p) cs[k] = j,cc[j] = k;
		mo = cc[ci[i]];
		for(j = 0;j < c;++j){
			for(k = 0;k < mo;++k) an[j][k][0] = an[j][k][1] = 0;
		}
		for(j = 0;j < c;++j){
			for(k = 0;k < mo;++k){
				tx = j;ty = k;
				nxt(tx,ty);
				to[j][k][0] = tx;to[j][k][1] = ty;
			}
		}
		for(j = 0;j < c;++j){
			dfs(j,cc[j],1);
			for(k = 0;k < mo;++k) if(!an[j][k][0]) dfs2(j,k);
		}
		for(j = 1;j <= n;++j){
			li u = a[j] % mo,v = b[j] % mo;
			if(!u && !v) continue;
			bool fg = 0;
			if(!u) swap(u,v),fg = 1;
			k = exgcd(u,mo,x,y);l = cs[k];
			if(x < 0) x += mo / k;
			(v *= x) %= mo;
			li a1 = an[l][v][0],a2 = an[l][v][1];
			if(fg) a2 = a1 - 1;
			as[j][2] = max(as[j][2],a2);
			crt(as[j][0],as[j][1],a1,a2 == -2 ? a2 : a2 % a1);
		}
	}
	for(i = 1;i <= n;++i){
		if(!a[i]) pc('0');
		else if(!b[i]) pc('1');
		else{
			if(as[i][1] != -2 && as[i][1] < as[i][2]) 
				as[i][1] += ((as[i][2] - as[i][1] - 1) / as[i][0] + 1) * as[i][0];
			print(as[i][1] + 1);
		} 
		pc('\n');
	}
	return 0;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值