2020 牛客 NOIP 赛前集训营(第四场)提高级 C – 斐波 题解

一道让我做得相当高兴的题目。

原题链接 (这是付费比赛。)

题意简述

给定长度为 nn 的序列 aa。定义函数 f(S)=TSF2(sTs)f(S)=\sum\limits_{T\subseteq S} F^2(\sum_{s\in T} s),也即“对于 SS 的所有子集,求 F(子集所有元素之和)的平方F(\text{子集所有元素之和})\text{的平方} 之和”。其中 F(x)F(x) 为斐波那契数列的第 xx 项。有F(0)=0,F(1)=1,F(x)=F(x1)+F(x2)(x>1)F(0)=0, F(1)=1, F(x)=F(x-1)+F(x-2) (x > 1)

你需要支持以下操作,操作总数为 mm

1 x y:将 axa_x 修改为 yy

2 l r:输出 i=lrj=irf({ai,ai+1,,aj})\sum\limits_{i=l}^{r}\sum\limits_{j=i}^{r}f(\{a_i, a_{i+1}, \cdots, a_{j}\}),对 998244353998244353 取模。

数据范围:对于 50%\text{50\%} 的数据,有 n,m100n,m\leq 100;对于 70%\text{70\%} 的数据,有 n,m1000n,m\leq 1000;对于 100%\text{100\%} 的数据,有 n,m105,ai105n,m\leq 10^5, a_i \leq 10^5

50 pts\textbf{50 pts}做法

有没有办法在多项式时间复杂度内回答单个询问呢?

枚举子集然后计算显然是 O(2nn)\mathrm{O}(2^nn) 的,不可接受。但我们经常对类似于“序列上连续一段的子集”上的函数求和时,考虑每加入一个新的元素构成的贡献。

具体来说,假如我们求出了 A=TSf(T),S={al,,ar}A=\sum\limits_{T \subseteq S}f(T), S=\{a_l, \cdots, a_r\},那么当我们考虑 A=TSf(T),S={al,,ar+1}A’=\sum\limits_{T \subseteq S’}f(T), S’=\{a_l, \cdots, a_{\color{blue}r+1}\} 时,显然有 A=A+TSf(T{ar+1}),S={al,,ar}A’=A+\sum\limits_{T \subseteq S}f(T\cup \{a_{r+1}\}), S=\{a_l, \cdots, a_r\}。而本题中向参数集合添加一元素就等于求和,所以,若我们能够将“原来的和 vv ”构成的 F2(v)F^2(v) 的和”与“新加入的元素的贡献”独立出来就能在 O(n)\mathrm{O}(n) 的时间内计算 i=lrf({al,,ai})\sum\limits_{i=l}^{r}f(\{a_l, \cdots, a_i\})

回想斐波那契数列的一些性质。现在知道非负整数 x,Δx, \Delta,则F(x+Δ)=F(x)F(Δ+1)+F(x1)F(Δ)F(x+\Delta)=F(x)F(\Delta+1)+F(x-1)F(\Delta)。我们高兴地发现,这样一来,只需要知道 v(T)=sTsv(T)=\sum_{s\in T} s,就能 O(1)\mathrm{O}(1) 算出加入另一个数后的 F(Δ+v)F(\Delta+v) 了。那么,假如我们知道 G(S)=TSF(v(T))H(S)=TSF(v(T)1),S={al,,ar}\begin{aligned}& G(S)=\sum_{T \subseteq S} F(v(T))\newline & H(S)=\sum_{T \subseteq S} F(v(T)-1), S=\{a_l, \cdots, a_r\}\end{aligned},记 Δ=ar+1\Delta=a_{r+1},就能算出 G(S{Δ})=G(S)+G(S)F(Δ+1)+H(S)F(Δ)H(S{Δ})=H(S)+G(S)F(Δ)+H(S)F(Δ1)\begin{aligned}G(S\cup \{\Delta\})=G(S)+G(S)F(\Delta+1)+H(S)F(\Delta) \\ H(S\cup \{\Delta\})=H(S)+G(S)F(\Delta)+H(S)F(\Delta-1)\end{aligned}

同理,我们扩展到平方项的情况。经过展开可以发现 F2(x+Δ)=F2(x)F2(Δ+1)+2F(x)F(x1)F(Δ+1)F(Δ)+F2(x1)F2(Δ) F^2(x+\Delta)=F^2(x)F^2(\Delta+1)+2F(x)F(x-1)F(\Delta+1)F(\Delta)+F^2(x-1)F^2(\Delta) ,其新增贡献全部可以拆分为 F2(v(T)),F(v(T))F(v(T1)),F2(v(T)1)F^2(v(T)),F(v(T))F(v(T-1)), F^2(v(T)-1)F(Δ)F(\Delta) 等项的乘积形式。具体来讲,记 P(S)=TSF2(v(T))=f(S)Q(S)=TSF2(v(T)1)K(S)=TSF(v(T))F(v(T)1),S={al,,ar}\begin{aligned} & P(S)=\sum_{T \subseteq S} F^2(v(T))=f(S)\\ & Q(S)=\sum_{T \subseteq S} F^2(v(T)-1)\\ & K(S)=\sum_{T \subseteq S} F(v(T))F(v(T)-1), S=\{a_l,\cdots,a_r\} \end{aligned},记 ar+1=Δa_{r+1}=\Delta,则有 P(S{Δ})=P(S)+P(S)F2(Δ+1)+Q(S)F2(Δ)+2K(S)F(Δ+1)F(Δ)Q(S{Δ})=Q(S)+Q(S)F2(Δ)+Q(S)F2(Δ1)+2K(S)F(Δ)F(Δ1)K(S{Δ})=K(S)+P(S)F(Δ)F(Δ+1)+Q(S)F(Δ)F(Δ1)+K(S)(F2(Δ)+F(Δ+1)F(Δ1))\begin{aligned} P(S\cup \{\Delta\})=&P(S)+P(S)F^2(\Delta+1)+Q(S)F^2(\Delta)+2K(S)F(\Delta+1)F(\Delta)\\ Q(S\cup \{\Delta\})=&Q(S)+Q(S)F^2(\Delta)+Q(S)F^2(\Delta-1)+2K(S)F(\Delta)F(\Delta-1)\\ K(S\cup \{\Delta\})=&K(S)+P(S)F(\Delta)F(\Delta+1)+Q(S)F(\Delta)F(\Delta-1)\\ &+K(S)(F^2(\Delta)+F(\Delta+1)F(\Delta-1)) \end{aligned}

所以,我们枚举询问区间中的 l[l,r]l’\in [l, r],每一次遍历序列都统一计算以 ll’ 为左端点的子区间答案之和,就可以在 O(n2)\mathrm{O}(n^2) 的时间内处理单个询问。

(其实评测机够快可以骗到70 pts\textbf{70 pts}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
inl ll pow2 (ll x) { return x * x % P; } inl int calc (int l, int r) { // 计算f([l, l]), ..., f([l, r])的和。 // 及时维护F^2(v)的和,F^2(v-1)的和以及F(v)F(v-1)的和,\ 分别记作F2vS, F2v_1S, FvFv_1S。 ll F2vS = 0, F2v_1S = 1, FvFv_1S = 0, dt, res = 0, Fd, Fd1, Fd_1, F2vdS, F2v_1dS, FvdFv_1dS, F2d; for (int i = l; i <= r; ++i) { dt = a[i], Fd = F[dt], Fd1 = F[dt + 1], Fd_1 = F[dt - 1], F2d = pow2 (Fd); F2vdS = (F2vS * pow2 (Fd1) + F2v_1S * F2d + Fd * Fd1 % P * FvFv_1S * 2) % P, F2v_1dS = (F2vS * F2d + F2v_1S * pow2 (Fd_1) + Fd * Fd_1 % P * FvFv_1S * 2) % P, FvdFv_1dS = (F2vS * Fd1 % P * Fd + FvFv_1S * ((F2d + Fd_1 * Fd1) % P) + F2v_1S * Fd_1 % P * Fd) % P; (F2vS += F2vdS) %= P, (F2v_1S += F2v_1dS) %= P, (FvFv_1S += FvdFv_1dS) %= P; res += F2vS; } return res % P; } inl int query (int l, int r) { ll res = 0; for (int i = l; i <= r; ++i) res += calc (i, r); return res % P; }

100 pts\textbf{100 pts}做法

我们发现这个转移式子的每一项都是前一项函数值的一次项该项的固定系数,那就很容易使用矩阵乘法加速递推了嘛。

具体来讲,我们设状态矩阵为A=[P(S)Q(S)K(S)]\mathbf{A}=\begin{bmatrix} P(S) & Q(S) & K(S) \end{bmatrix},则对于ai=Δa_i=\Delta,我们有如下转移: Bi=[1+F2(Δ+1)F2(Δ)F(Δ+1)F(Δ)F2(Δ)1+F2(Δ1)F(Δ)F(Δ1)2F(Δ)F(Δ+1)2F(Δ)F(Δ1)1+F2(Δ)+F(Δ+1)F(Δ1)]\mathbf{B}_i=\begin{bmatrix} 1+F^2(\Delta+1) & F^2(\Delta) & F(\Delta+1)F(\Delta)\\ F^2(\Delta) & 1+F^2(\Delta-1) & F(\Delta)F(\Delta-1)\\ 2F(\Delta)F(\Delta+1) & 2F(\Delta)F(\Delta-1) & 1+F^2(\Delta)+F(\Delta+1)F(\Delta-1) \end{bmatrix}

f({al,,ar})=([010]×i=lrBi)1,1f(\{a_l, \cdots, a_r\})=(\begin{bmatrix}0 & 1 & 0\end{bmatrix}\times \prod_{i=l}^{r}\mathbf{B}_i)_{1,1}。同时根据矩阵乘法满足左右结合律分配律,我们使用线段树维护区间 [l,r][l, r] 的以下信息:

  • 区间内所有转移矩阵的积 i=lrBi\prod\limits_{i=l}^{r}\mathbf{B}_i
  • 区间转移矩阵前缀积之和 i=lrj=liBj\sum\limits_{i=l}^{r}\prod\limits_{j=l}^{i}\mathbf{B}_j
  • 区间转移矩阵后缀积之和(仍然是左乘i=lrj=irBj\sum\limits_{i=l}^{r}\prod\limits_{j=i}^{r}\mathbf{B}_j
  • 区间所有子区间的转移矩阵积的和 i=lrj=irk=ijBk\sum\limits_{i=l}^{r}\sum\limits_{j=i}^{r}\prod\limits_{k=i}^{j}\mathbf{B}_k

在建树和查询时合并两个区间,就可利用左右子区间的信息轻松计算。单点修改同理。详细实现参见代码。

时间复杂度 O(33mlogn)\mathrm{O}(3^3 m\log n),需要注意矩乘的常数问题。

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
  119. 119
  120. 120
  121. 121
  122. 122
  123. 123
  124. 124
  125. 125
  126. 126
  127. 127
  128. 128
  129. 129
  130. 130
  131. 131
  132. 132
  133. 133
  134. 134
  135. 135
  136. 136
  137. 137
  138. 138
  139. 139
  140. 140
  141. 141
  142. 142
  143. 143
  144. 144
  145. 145
  146. 146
  147. 147
  148. 148
  149. 149
  150. 150
  151. 151
  152. 152
  153. 153
  154. 154
  155. 155
  156. 156
  157. 157
  158. 158
  159. 159
  160. 160
  161. 161
#include <bits/stdc++.h> using namespace std; #define inl inline template <typename T> inl bool read (T &x) { x = 0; int f = 1; char c = getchar (); for (; c != EOF && !isdigit (c); c = getchar ()) if (c == '-') f = -1; if (c == EOF) return 0; for (; c != EOF && isdigit (c); c = getchar ()) x = (x<<1) + (x<<3) + (c^48); x *= f; return 1; } template <typename T, typename... Targs> inl bool read (T &x, Targs&... args) { return read (x) && read (args...); } template <typename T> void print (T x) { if (x < 0) putchar ('-'), x = -x; if (x > 9) print (x / 10); putchar ('0' + x % 10); } template <typename T, typename... Targs> inl void print (T x, Targs... args) { print (x), putchar (' '), print (args...); } #define reint register int #define newl putchar('\n') typedef long long ll; // typedef unsigned long long ull; // typedef __int128 lll; // typedef long double llf; typedef pair <int, int> pint; #define fst first #define scd second #define all(p) (p).begin (), (p).end () constexpr int N = 1e5 + 10, P = 998244353, G = N; int n, a[N], l, r, x, type, m, F[G]; struct matrix { int mat[3][3], x, y; /* Constructors */ using inilst = initializer_list <initializer_list<int>>; inl matrix () {} inl void init (const inilst& _mat) { auto it = _mat.begin (); for (int i = 0; i < x; ++i) { memset (mat[i], 0, sizeof mat[i]); if (it != _mat.end ()) { for (int j = 0; j < y; ++j) mat[i][j] = *(it->begin () + j); ++it; } } } inl matrix (int _x, int _y, const inilst& _mat = {}) : x (_x), y (_y) { init (_mat); } inl matrix (const inilst& _mat) { x = _mat.size (), y = 0; for (auto it = _mat.begin (); it != _mat.end (); ++it) y = max (y, (int) it->size ()); init (_mat); } /* Operators */ inl const int* operator [] (int _x) const { return mat[_x]; } inl int* operator [] (int _x) { return mat[_x]; } inl const matrix operator * (const matrix &b) const { // 特化1x3和3x3矩阵乘法以加速。 matrix c (x, b.y); c.mat[0][0] = (1ll * mat[0][0] * b.mat[0][0] + 1ll * mat[0][1] * b.mat[1][0] + 1ll * mat[0][2] * b.mat[2][0]) % P, c.mat[0][1] = (1ll * mat[0][0] * b.mat[0][1] + 1ll * mat[0][1] * b.mat[1][1] + 1ll * mat[0][2] * b.mat[2][1]) % P, c.mat[0][2] = (1ll * mat[0][0] * b.mat[0][2] + 1ll * mat[0][1] * b.mat[1][2] + 1ll * mat[0][2] * b.mat[2][2]) % P; if (x == 3) c.mat[1][0] = (1ll * mat[1][0] * b.mat[0][0] + 1ll * mat[1][1] * b.mat[1][0] + 1ll * mat[1][2] * b.mat[2][0]) % P, c.mat[1][1] = (1ll * mat[1][0] * b.mat[0][1] + 1ll * mat[1][1] * b.mat[1][1] + 1ll * mat[1][2] * b.mat[2][1]) % P, c.mat[1][2] = (1ll * mat[1][0] * b.mat[0][2] + 1ll * mat[1][1] * b.mat[1][2] + 1ll * mat[1][2] * b.mat[2][2]) % P, c.mat[2][0] = (1ll * mat[2][0] * b.mat[0][0] + 1ll * mat[2][1] * b.mat[1][0] + 1ll * mat[2][2] * b.mat[2][0]) % P, c.mat[2][1] = (1ll * mat[2][0] * b.mat[0][1] + 1ll * mat[2][1] * b.mat[1][1] + 1ll * mat[2][2] * b.mat[2][1]) % P, c.mat[2][2] = (1ll * mat[2][0] * b.mat[0][2] + 1ll * mat[2][1] * b.mat[1][2] + 1ll * mat[2][2] * b.mat[2][2]) % P; return c; } inl matrix & operator *= (const matrix &b) { return *this = (*this) * b; } inl const matrix operator + (const matrix &b) const { return { { (mat[0][0] + b.mat[0][0]) % P, (mat[0][1] + b.mat[0][1]) % P, (mat[0][2] + b.mat[0][2]) % P }, { (mat[1][0] + b.mat[1][0]) % P, (mat[1][1] + b.mat[1][1]) % P, (mat[1][2] + b.mat[1][2]) % P }, { (mat[2][0] + b.mat[2][0]) % P, (mat[2][1] + b.mat[2][1]) % P, (mat[2][2] + b.mat[2][2]) % P } }; } inl matrix & operator += (const matrix &b) { return *this = *this + b; } }; inl int pow2 (ll x) { return x * x % P; } const matrix trans (int d) { int Fd = F[d], Fd_1 = F[d - 1], Fd1 = F[d + 1], F2d = pow2 (Fd); return { { 1 + pow2 (Fd1), F2d, 1ll * Fd1 * Fd % P }, { F2d, 1 + pow2 (Fd_1), 1ll * Fd * Fd_1 % P }, { 2ll * Fd * Fd1 % P, 2ll * Fd * Fd_1 % P, (1 + F2d + 1ll * Fd1 * Fd_1 % P) % P } }; } struct seg_tree { struct info { matrix segs/* 子区间矩阵积之和 */, pres/* 前缀积之和 */, sufs/* 后缀积之和 */, prod; }; struct node { int l, r; info dta; } t[N<<2]; int idx[N]; const info colle (const info &ls, const info &rs) { return { ls.sufs * rs.pres + ls.segs + rs.segs, ls.pres + ls.prod * rs.pres, ls.sufs * rs.prod + rs.sufs, ls.prod * rs.prod }; } inl void renew (int x, int d) { auto tr = trans (d); t[x].dta = { tr, tr, tr, tr }; } #define post(x) (t[x].dta = colle (t[x<<1].dta, t[x<<1|1].dta)) void build (int x, int l, int r) { t[x].l = l, t[x].r = r; if (l == r) return idx[l] = x, renew (x, a[l]); int mid = t[x].l + t[x].r >> 1; build (x<<1, l, mid); build (x<<1|1, mid + 1, r); post (x); } inl void upd (int pos) { int x = idx[pos]; renew (x, a[pos]); while (x > 1) x >>= 1, post (x); } info query (int x, int L, int R) { if (t[x].l >= L && t[x].r <= R) return t[x].dta; int mid = t[x].l + t[x].r >> 1, sta = ((R > mid)<<1)|(L <= mid); if (sta < 3) return query (x<<1|(sta-1), L, R); return colle (query (x<<1, L, R), query (x<<1|1, L, R)); } } seg; int main () { /* MOCK NOIP 20220618 C - 斐波 吴秋实 */ F[1] = 1; for (int i = 2; i < G; ++i) F[i] = (F[i - 1] + F[i - 2]) % P; read (n, m); for (int i = 1; i <= n; ++i) read (a[i]); seg.build (1, 1, n); for (int i = 1; i <= m; ++i) { read (type); if (type == 1) read (x), read (a[x]), seg.upd (x); else read (l, r), print ((matrix {{ 0, 1, 0 }} * seg.query (1, l, r).segs)[0][0]), newl; } return 0; }

解二

生成函数(我不会)。

解三

lty的解法。使用通项公式 F(n)=15[(1+52)n(152)n]F(n)=\dfrac{1}{\sqrt{5}}\left[\left(\dfrac{1+\sqrt{5}}{2}\right)^n-\left(\dfrac{1-\sqrt{5}}{2}\right)^n\right] 并利用上文方法作类似合并。

但令我相当迷惑的是,55p=998244353p=998244353 下不存在二次剩余。怎么在模意义下表示 5\sqrt{5} 啊?!直接存储系数吗?

  • 2022年6月20日