[BZOJ2432][Noi2011]兔农(数论+矩阵乘法)

Address

洛谷P2020
BZOJ2432
LOJ#2442

Solution

显然是斐波那契数列
但是递推式加入了鬼畜的条件:
f [ i ] = f [ i 1 ] + f [ i 2 ] [ f [ i 1 ] + f [ i 2 ] 1 (   m o d   k ) ] f[i]=f[i-1]+f[i-2]-[f[i-1]+f[i-2]\equiv1(\bmod k)]
我们考虑对在模 k k 意义下的 f f 进行讨论。
可以发现,当 i > 2 i>2 时,如果是满足 f [ i 1 ] + f [ i 2 ] 1 (   m o d   k ) f[i-1]+f[i-2]\equiv1(\bmod k) 最小的 i i
那么 f [ i ] = 0 f[i]=0 ,而:
f [ i + 1 ] = f [ i ] + f [ i 1 ] = f [ i 1 ] f[i+1]=f[i]+f[i-1]=f[i-1]
同样地, f [ i + 2 ] = f [ i + 1 ] + f [ i ] = f [ i 1 ] f[i+2]=f[i+1]+f[i]=f[i-1]
于是接下去在没有模 k k 1 1 的情况时,有 f [ i + x ] = f [ i 1 ] × F i b ( x ) f[i+x]=f[i-1]\times Fib(x)
F i b ( x ) Fib(x) 为斐波那契数列第 x x 项)
考虑如果存在最小 j > i j>i 满足 f [ j 1 ] + f [ j 2 ] 1 (   m o d   k ) f[j-1]+f[j-2]\equiv1(\bmod k)
那么有 f [ i 1 ] × F i b ( j i ) 1 (   m o d   k ) f[i-1]\times Fib(j-i)\equiv 1(\bmod k)
用 exgcd 求出 f [ i 1 ] f[i-1] 的逆元 f [ i 1 ] 1 f[i-1]^{-1} ,找到一个最小的 j > i j>i
满足 F i b ( j i ) = f [ i 1 ] 1 Fib(j-i)=f[i-1]^{-1} ,则 f [ j 1 ] + f [ j 2 ] 1 (   m o d   k ) f[j-1]+f[j-2]\equiv1(\bmod k)
如果 f [ i 1 ] f[i-1] 的逆元不存在或者不存在 F i b ( j i ) = f [ i 1 ] 1 Fib(j-i)=f[i-1]^{-1} ,则在 i i 之后不会出现模 k k 1 1 的情况。
又由于 F i b Fib k k 的余数的最小正周期为 O ( k ) O(k) ,所以考虑先求出最小正周期 r r ,然后预处理出 1 1 r + 1 r+1 F i b Fib
然后求出 F i b [ 1 r + 1 ] Fib[1\dots r+1] 的逆元 i n v [ 1 r + 1 ] inv[1\dots r+1]
定义 F i b x Fib_x 为:
F i b x ( 1 ) = F i b x ( 2 ) = x Fib_x(1)=Fib_x(2)=x
F i b x ( n ) = F i b x ( n 1 ) + F i b x ( n 2 ) , n > 2 Fib_x(n)=Fib_x(n-1)+Fib_x(n-2),n>2
而定义 l e n x len_x 表示满足 l e n x > 2 len_x>2 F i b x ( l e n x ) 1 (   m o d   k ) Fib_x(len_x)\equiv1(\bmod k) 的最小的 l e n x len_x
由上面的推论得到,
如果 i > 2 i>2 i n v [ i ] inv[i] i n v [ 1... i 1 ] inv[1...i-1] 中没有出现过,则 l e n i n v [ i ] = i len_{inv[i]}=i
其他的 l e n len 都等于 \infty
先设初始答案为 s = f [ 0 ] = 0 s=f[0]=0 且设 x = n u m = 1 x=num=1
其中 n u m = f [ x + 1 ] num=f[x+1] (模 k k 意义下), s s 表示 f [ x ] f[x] (模 p p 意义下),
x x 表示当前讨论 f [ x + 1 x + l e n n u m ] f[x+1\dots x+len_{num}]
循环进行下面算法:
(1)如果 l e n n u m len_{num}\ne\infty ,那么会有 f [ x + l e n n u m ] = 0 f[x+len_{num}]=0 。这时候可以用矩阵乘法将 s s f [ x ] f[x] 转移到 f [ x + l e n n u m ] f[x+len_{num}] 。注意,如果 n f [ x + l e n n u m ] n\le f[x+len_{num}] 则转移到 f [ n ] f[n] 并输出即可。
然后 x x + l e n n u m + 1 x\leftarrow x+len_{num}+1 s x 1 s\leftarrow x-1 n u m F i b n u m ( l e n n u m 1 ) num\leftarrow Fib_{num}(len_{num-1})
(2)否则 l e n n u m = len_{num}=\infty ,直接转移到 n n 即可。
这样会 TLE 。但如果 n u m num 出现了重复,就意味着 n u m num 出现了循环节。通过一个循环节的转移系数相同。
如果循环节长度为 m m ,要转移 n n 次,就只需要预处理出通过一个循环节的转移矩阵 A A 后,求矩阵 A n m A^{\lfloor\frac nm\rfloor} 后,剩下的 n   m o d   m n\bmod m 次暴力转移即可。
复杂度 O ( k log ) O(k\log)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;

template <class T>
T Min(T a, T b) {return a < b ? a : b;}

typedef long long ll;

const int N = 6e6 + 5, M = 3e6 + 5, E = 4;
int k, m, ZZQ, a[N], inv[M], tot, st[M], u[M], len[M], St, Ed, vis[M];
ll n, sum;

struct matr
{
	int n, m, a[E][E];
	
	matr() {}
	matr(int _n, int _m) :
		n(_n), m(_m) {memset(a, 0, sizeof(a));}
		
	friend inline matr operator * (matr a, matr b)
	{
		int i, j, k;
		matr res = matr(a.n, b.m);
		For (i, 1, res.n) For (j, 1, res.m) For (k, 1, a.m)
			res.a[i][j] = (res.a[i][j] + 1ll * a.a[i][k] * b.a[k][j] % ZZQ) % ZZQ;
		return res;
	}
	
	friend inline matr operator ^ (matr a, ll b)
	{
		int i;
		matr res = matr(a.n, a.m);
		For (i, 1, res.n) res.a[i][i] = 1;
		while (b)
		{
			if (b & 1) res = res * a;
			a = a * a;
			b >>= 1;
		}
		return res;
	}
} A, D, J, V, K;

void exgcd(int a, int b, int &x, int &y)
{
	if (!b) return (void) (x = 1, y = 0);
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}

int getinv(int a, int ZZQ)
{
	if (__gcd(a, ZZQ) != 1) return 0;
	int x, y;
	exgcd(a, ZZQ, x, y);
	x = x % ZZQ + ZZQ;
	return x >= ZZQ ? x - ZZQ : x;
}

matr frN(int l, int r, int st1, int st2, ll n)
{
	int i;
	D = matr(2, 1);
	D.a[1][1] = st1; D.a[2][1] = st2;
	For (i, l, r)
	{
		if (n <= 0) return D;
		int rs = Min(n, 1ll * len[i]);
		D = (A ^ rs) * D;
		if (rs >= len[i] - 1) D.a[1][1] = (D.a[1][1] - 1 + ZZQ) % ZZQ;
		if (rs == len[i]) D.a[2][1] = (D.a[2][1] - 1 + ZZQ) % ZZQ;
		n -= rs;
	}
	return D;
}

int main()
{
	int i;
	cin >> n >> k >> ZZQ;
	a[1] = a[2] = 1;
	For (i, 3, 6000000)
		a[i] = (a[i - 1] + a[i - 2]) % k;
	For (i, 3, 6000000)
		if (a[i] == 1 && a[i - 1] == 1)
		{
			m = i - 1; break;
		}
	For (i, 1, m) inv[i] = a[i] ? getinv(a[i], k) : 0;
	For (i, 3, m) if (!vis[a[i]])
	{
		if (inv[i]) st[inv[i]] = i;
		vis[a[i]] = 1;
	}
	memset(vis, 0, sizeof(vis));
	u[1] = 1;
	For (i, 2, k + 1)
	{
		len[i - 1] = st[u[i - 1]];
		u[i] = 1ll * u[i - 1] * a[st[u[i - 1]] - 1] % k;
		if (!u[i]) {St = i; Ed = -1; break;}
		else if (vis[u[i]]) {St = vis[u[i]]; Ed = i - 1; break;}
		vis[u[i]] = i;
	}
	A = matr(2, 2);
	A.a[1][1] = A.a[2][1] = A.a[1][2] = 1;
	For (i, 1, St - 1) sum += len[i];
	matr firs = frN(1, St - 1, 1, 0, Min(n, sum));
	if (n <= sum) return cout << firs.a[2][1] << endl, 0;
	n -= sum; sum = 0;
	if (Ed == -1) return cout << ((A ^ n) * firs).a[2][1] << endl, 0;
	For (i, St, Ed) sum += len[i];
	J = V = matr(3, 3);
	V.a[1][1] = V.a[2][2] = V.a[3][3] = 1;
	J.a[1][1] = J.a[1][2] = J.a[2][1] = 1; J.a[3][3] = 1;
	K = J; K.a[1][3] = ZZQ - 1;
	For (i, St, Ed)
		V = J * K * (J ^ len[i] - 2) * V;
	firs.n = 3; firs.a[3][1] = 1;
	firs = (V ^ n / sum) * firs;
	firs = frN(St, Ed, firs.a[1][1], firs.a[2][1], n % sum);
	cout << firs.a[2][1] << endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/82898454