CF575A

2023-08-10

传送门

简要题意

给定序列 $s_0,s_1,s_2,\dots,s_{n-1}$,对于 $i \ge n$,特殊给出 $m$ 个 $s_i$,满足 $s_i \not= s_{i \bmod n}$,其他 $s_i$ 都满足 $s_i = s_{i \bmod n}$。

定义序列 $F$,$F$ 满足递推式 $F_n = s_{n-1} \cdot F_{n-1} + s_{n-2} \cdot F_{n-2}$,其中 $F_0 = 0, F_1 = 1$。

给出 $K, P$,请你求出 $F_K \bmod P$ 的值。

分析

$F$ 序列和斐波那契序列很像,因此考虑用矩阵来做转移:

\[\begin{pmatrix} f_{i-2} & f_{i-1} \end{pmatrix} \begin{pmatrix} 0 & s_{i-2} \\ 1 & s_{i-1} \end{pmatrix} = \begin{pmatrix} f_{i-1} & f_{i} \end{pmatrix}\]

但是这样定义矩阵的话,对于 $m$ 个特殊的 $s_i$,每个特殊值会影响两个矩阵,写起来有一车细节,很不舒服。所以我们把矩阵递推式改一改:

\[\begin{pmatrix} f_{i-2} s_{i-2} & f_{i-1} \end{pmatrix} \begin{pmatrix} 0 & 1 \\ s_{i-1} & s_{i-1} \end{pmatrix} = \begin{pmatrix} f_{i-1} s_{i-1} & f_{i} \end{pmatrix}\]

这么一来,每个特殊值只会影响一个矩阵。下文中,记 $A_i$ 表示 $s_i$ 对应的形如 $\begin{pmatrix} 0 & 1 \ s_i & s_i \end{pmatrix}$ 的矩阵。

当 $m = 0$ 时,我们有

\[\begin{pmatrix} f_{k-1}s_{k-1} & f_{k} \end{pmatrix} = \begin{pmatrix} f_0s_0 & f_1 \end{pmatrix} \times \prod_{i=1}^k A_i\]

当 $m \not= 0$ 时,我们先把 $m$ 个特殊值按下标排序,得到 $s_{indx_1},s_{indx_2},\dots,s_{indx_m}$,其中 $indx$ 数组代表特殊值的下标。

考虑到 $m$ 个特殊值把 $1$ 到 $k$ 分成了 $m+1$ 段,那我们就一段一段的乘起来。每一段的长度都可以表示为一个 $pn+q$ 的形式,而这一段内的矩阵都是重复的。因此,我们可以维护任意长度小于等于 $n$ 的区间矩阵之积,快速幂即可。对于特殊值,单独乘起来即可。

维护长度小于等于 $n$ 的区间矩阵之积可以用线段树。

一些细节

  • 线段树可以维护 $0$ 到 $2n-1$ 的矩阵积,这样不用特判 $l \bmod n$ 与 $r \bmod n$ 之间的大小关系。注意要开八倍空间。

  • 特判 $k = 0$ 的情况,因为我们的初始矩阵就是 $\begin{pmatrix} f_0s_0 & f_1 \end{pmatrix}$。

不懂可以配合代码食用:

#include <bits/stdc++.h>
#define int long long
#define FOR(i, a, b) for(int i = a; i <= b; i++)
#define ROF(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 5e4+5;
int s[N], k, p, n, m;
//矩阵板子 
struct mat {
	int a[3][3];
	mat(int x = 0){
		memset(a, 0, sizeof(a));
		if (x) a[1][1] = a[2][2] = 1;
	}
	void print() {
		FOR(i, 1, 2) {
			FOR(j, 1, 2) cout << a[i][j] << ' ';
			cout << endl;
		}
		return;
	}
} ans;
mat operator*(mat a, mat b) {
	mat c;
	FOR(i, 1, 2) FOR(j, 1, 2) FOR(k, 1, 2) c.a[i][j] = (c.a[i][j] + a.a[i][k] * b.a[k][j] % p) % p;
	return c;
}
mat qpow(mat a, int p) {
	mat res(1);
	while (p) {
		if (p & 1) res = res * a;
		a = a * a;
		p >>= 1;
	}
	return res;
}
mat Mat(int a, int b, int c, int d) {  //给矩阵赋值 
	mat A;
	A.a[1][1] = a, A.a[1][2] = b, A.a[2][1] = c, A.a[2][2] = d;
	return A;
}

//线段树板子 
struct node { mat a; int l, r; } t[N << 3];  //八倍空间 
void pushup(int p) {
	t[p].a = t[p << 1].a * t[p << 1 | 1].a;
	return;
}
void build(int L, int R, int p) {
	t[p].l = L, t[p].r = R;
	if (L == R) {
		t[p].a = Mat(0, 1, s[L % n], s[L % n]);
		return;
	}
	int mid = (L + R) >> 1;
	build(L, mid, p << 1), build(mid+1, R, p << 1 | 1);
	pushup(p);
	return;
}
mat query(int L, int R, int p) {
	if (L <= t[p].l && t[p].r <= R) return t[p].a;
	int mid = (t[p].l + t[p].r) >> 1;
	mat res(1);
	if (L <= mid) res = res * query(L, R, p << 1);
	if (mid+1 <= R) res = res * query(L, R, p << 1 | 1);
	return res;
}

struct change { int indx, v; } a[N];   //记录特殊值的信息 
mat get(int l, int r) {                //获取[l,r]之间的矩阵积,[l,r]是长度小于等于n的区间 
	mat tmp(1);
	if (l > r) return tmp;             //l>r就返回单位矩阵 
	l %= n, r %= n;
	if (l > r) r += n;
	return query(l, r, 1);             //维护[0,2n-1]区间矩阵积的目的就在这里 
}
mat work(int l, int r) {               //获取任意[l,r]之间的矩阵积 
	mat tmp(1);
	if (l > r) return tmp;             //l>r就返回单位矩阵 
	//pn+q中,p=(r-l+1)/n,然后剩下的区间一定长度小于n,左端点调整完后直接get即可 
	tmp = tmp * qpow(get(l, l+(r-l+1)/n*n-1), (r-l+1) / n);
	tmp = tmp * get(l+(r-l+1)/n*n, r);
	return tmp;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> k >> p >> n;
	FOR(i, 0, n-1) cin >> s[i];
	build(0, n+n-1, 1);          //[0,2n-1]区间矩阵积 
	cin >> m;
	FOR(i, 1, m) cin >> a[i].indx >> a[i].v;
	if (k == 0) {
		cout << 0;
		return 0;
	}
	sort(a+1, a+m+1, [](change A, change B){ return A.indx < B.indx; }); //按下标排序 
	int i = 1;
	ans = Mat(0, 1, 0, 0);
	for(; i <= m && a[i].indx < k; i++) {
		int S = a[i-1].indx, T = a[i].indx;
		ans = ans * work(S+1, T-1) * Mat(0, 1, a[i].v % p, a[i].v % p);
	}
	if (a[i-1].indx < k) {
		int S = a[i-1].indx, T = k-1;
		ans = ans * work(S+1, T);
	}
	cout << ans.a[1][2] % p;
	return 0;
}