听说大赏的意思好像和我之前认为的不太一样?
没文化啊.png
「BJOI2019」奥术神杖 为什么剧情里总是向过往寻求力量呢 (
题目链接
解题思路 1 月份做过结果忘地一干二净
设选择后神杖上的宝石后, 得出的生效的咒语序列为 $a_i$. 如果答案比 $x$ 优, 那么有
运用一些运算的技巧, 有
0-1 分数规划即可. 现在的重点在于计算中间和式的最大值.
考虑在 AC 自动机上 DP. 设 $f(i, u)$ 表示匹配到 $T$ 中第 $i$ 个位置, 以及 AC 自动机上第 $u$ 个节点在最大值.
那么根据 $T_i$ 处的字符转移即可, 输出方案可在 DP 时记录每次转移的选择就好了. 注意在处理权值时, 需要将 $u$ 的权值加到 $\operatorname{fail}(u)$ 上, 以及不要忽略多个串在同一节点上的情况.
记字符集大小为 $c$, 那么时间复杂度为 $O(nsc\log V)$.
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <cmath> #include <cstdio> #include <cstring> using namespace std;template <typename T> inline bool ckmax (T& a, const T& b) { return (a < b)? (a = b, true ): false ; } const int MAXN = 15e2 + 5 , SIGMA = 10 ;const double EPS = 1e-9 , INFD = 1e9 * MAXN;inline int dcmp (const double & p) { return (fabs (p) < EPS)? 0 : (p < 0 ? -1 : 1 ); } int n, m;char T[MAXN], S[MAXN];double f[MAXN][MAXN];int g[MAXN][MAXN][2 ];namespace AC { int ch[MAXN][SIGMA], fail[MAXN], size[MAXN], nidx; double A[MAXN], val[MAXN]; inline void init () { nidx = 1 ; } inline void insert (const double & w) { int u = 1 , lgt = strlen (S + 1 ); for (int i = 1 ; i <= lgt; ++i) { int c = S[i] - '0' ; if (!ch[u][c]) ch[u][c] = ++nidx; u = ch[u][c]; } val[u] += w, ++size[u]; } void getfail () { static int Q[MAXN], head, tail; Q[head = 1 ] = tail = 0 , fail[1 ] = 1 ; for (int c = 0 ; c < SIGMA; ++c) { int & v = ch[1 ][c]; if (v) Q[++tail] = v, fail[v] = 1 ; else v = 1 ; } while (head <= tail) { int u = Q[head++]; size[u] += size[fail[u]], val[u] += val[fail[u]]; for (int c = 0 ; c < SIGMA; ++c) { int & v = ch[u][c]; if (v) Q[++tail] = v, fail[v] = ch[fail[u]][c]; else v = ch[fail[u]][c]; } } } bool check (const double & Mid, bool flag = false ) { for (int u = 1 ; u <= nidx; ++u) A[u] = val[u] - Mid * size[u]; for (int i = 0 ; i <= n; ++i) for (int u = 1 ; u <= nidx; ++u) f[i][u] = -INFD; f[0 ][1 ] = 0 ; for (int i = 1 ; i <= n; ++i) for (int u = 1 ; u <= nidx; ++u) if (f[i - 1 ][u] > -INFD) { if (T[i] == '.' ) { for (int c = 0 ; c < SIGMA; ++c) { const int & v = ch[u][c]; if (ckmax (f[i][v], f[i - 1 ][u] + A[v])) g[i][v][0 ] = c, g[i][v][1 ] = u; } } else { const int c = T[i] - '0' , &v = ch[u][c]; if (ckmax (f[i][v], f[i - 1 ][u] + A[v])) g[i][v][0 ] = c, g[i][v][1 ] = u; } } int v = 1 ; for (int u = 2 ; u <= nidx; ++u) if (f[n][u] > f[n][v]) v = u; if (flag) for (int u = v, i = n; i; --i) S[i] = g[i][u][0 ] + '0' , u = g[i][u][1 ]; return dcmp (f[n][v]) > 0 ; } } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif AC::init (); scanf ("%d%d%s" , &n, &m, T + 1 ); for (int w, i = 1 ; i <= m; ++i) scanf ("%s%d" , S + 1 , &w), AC::insert (log (w)); AC::getfail (); double L = 0.0 , R = log (INFD); while (dcmp (R - L) > 0 ) { double Mid = (L + R) / 2.0 ; if (AC::check (Mid)) L = Mid; else R = Mid; } AC::check (L, true ), puts (S + 1 ); return 0 ; }
「BJOI2019」勘破神机 题目链接
解题思路 先假设我们已经知道了第 $n$ 项的答案为 $h_n$. 那么所求即为 $\frac{1}{R - L + 1}$ 乘上
后者是下降幂的形式, 可以联想到
其中 $\begin{bmatrix} n \ m \end{bmatrix}$ 表示第一类 Stirling 数. 此处可直接使用有符号第一类 Stirling 数来消去 $-1$.
那么原式可化作
这就启发我们去求 $h_n$ 的通项公式.
先考虑 $m = 2$ 的情况, 此时是个经典问题. 记 Fibonacci 数列为 $f_n$, 且有 $f_0 = 0, f_1 = 1$, 那么 $f_{n + 1}$ 即为 $2 \times n$ 网格的填充方案, 此时直接将 $L, R + 1$.
根据 Fibonacci 数列的递推公式, 并利用特征方程可以解出 $f_n$ 的通项为
设 $A = \frac{1}{\sqrt 5}, B = -\frac{1}{\sqrt 5}, x = \frac{1 + \sqrt 5}{2}, y = \frac{1 - \sqrt 5}{2}$, 那么 $f_n = A x ^ n + B y ^ n$.
带入到原式继续化简, 可得
后者为等比数列部分和, 直接计算 $O(\log R)$ 即可. 时间复杂度 $O(k ^ 2 \log R)$.
此时还有一个问题. 注意到 $5 ^ {(998244353 - 1) / 2} \equiv -1 \pmod {998244353}$, 即模意义下 $\sqrt{5}$ 不存在. 此时可运用称之为 “扩域” 的技巧, 也就是将数表示为 $x + \sqrt 5 y$ 的形式, 用类似于模意义下复数的方式进行运算. 同时, 最终结果一定为整数, 取 $x$ 值即可.
现在考虑 $m = 3$ 的情况.
手玩之后可以得出, 除去 $n = 2$ 的情况之外, 仅存在两种方式, 使得长度为偶数的网格被铺满, 且不是若干长度较小的偶数网格拼接而成. 同时, $n$ 为奇数时方案数为 $0$.
设 $g_n$ 为铺满 $3 \times 2n$ 网格的方案数. 则根据上述性质, 有
利用一些运算的技巧, 可得
利用之前的方法, 可以得出
直接算就完事了.
时间复杂度 $O(k ^ 2 \log R)$. 然而存在时间复杂度更为优秀的神仙做法…
代码实现 在利用快速幂计算 $x + \sqrt{5} y$ 的幂时, 不要天真地把指数模 $P - 1$…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <cstdio> #include <cassert> using namespace std;typedef long long LL;const int MAXK = 5e2 + 5 , P = 998244353 ;inline int Mod (const int & x) { return x + (x >> 31 & P); }inline int mns (const int & a, const int & b) { return Mod (a - b); }inline int pls (const int & a, const int & b) { return Mod (a + b - P); }inline int mul (const int & a, const int & b) { return 1LL * a * b % P; }int stl[MAXK][MAXK];int fac[MAXK], ifac[MAXK], inv[MAXK];inline int C (int n, int m) { return (n < m)? 0 : mul (mul (fac[n], ifac[m]), ifac[n - m]); } int fpow (int base, int b) { int ret = 1 ; while (b > 0 ) { if (b & 1 ) ret = mul (ret, base); base = mul (base, base), b >>= 1 ; } return ret; } void PreDone (int N) { inv[0 ] = inv[1 ] = 1 ; for (int i = 2 ; i <= N; ++i) inv[i] = mul (inv[P % i], P - P / i); ifac[0 ] = fac[0 ] = 1 ; for (int i = 1 ; i <= N; ++i) fac[i] = mul (i, fac[i - 1 ]), ifac[i] = mul (inv[i], ifac[i - 1 ]); stl[1 ][1 ] = 1 ; for (int i = 2 ; i <= N; ++i) for (int j = 1 ; j <= i; ++j) stl[i][j] = mns (stl[i - 1 ][j - 1 ], mul (i - 1 , stl[i - 1 ][j])); } LL Sqrt; struct Int { int x, y; Int (int _x = 0 , int _y = 0 ): x (_x), y (_y) { } Int operator + (const Int& rhs) const { return Int (pls (x, rhs.x), pls (y, rhs.y)); } Int operator - (const Int& rhs) const { return Int (mns (x, rhs.x), mns (y, rhs.y)); } Int operator * (const Int& rhs) const { int ac = mul (x, rhs.x), ad = mul (x, rhs.y), bc = mul (y, rhs.x); return Int (pls (ac, Sqrt * y % P * rhs.y % P), pls (bc, ad)); } Int operator / (const Int& rhs) const { int ac = mul (x, rhs.x), ad = mul (x, rhs.y), bc = mul (y, rhs.x); int iv = fpow (mns (mul (rhs.x, rhs.x), Sqrt * rhs.y % P * rhs.y % P), P - 2 ); return Int (mns (ac, Sqrt * y % P * rhs.y % P), mns (bc, ad)) * iv; } Int operator += (const Int& rhs) { return x = pls (x, rhs.x), y = pls (y, rhs.y), *this ; } Int operator -= (const Int& rhs) { return x = mns (x, rhs.x), y = pls (y, rhs.y), *this ; } Int operator *= (const Int& rhs) { return *this = *this * rhs; } }; inline Int conj (const Int& x) { return Int (x.x, mns (0 , x.y)); }Int fpowInt (Int base, LL b) { Int ret = Int (1 , 0 ); for ( ; b > 0 ; base *= base, b >>= 1 ) if (b & 1 ) ret *= base; return ret; } inline Int Sum (const LL& L, const LL& R, const Int& q) { if (q.x == 1 && q.y == 0 ) return (R - L + 1 ) % P; return (fpowInt (q, R + 1 ) - fpowInt (q, L)) / (q - 1 ); } int solve (LL L, LL R, const int & k, const int & m) { static Int pa[MAXK], pb[MAXK], px[MAXK], py[MAXK], A, x; int lgt = (R - L + 1 ) % P; if (m == 2 ) Sqrt = 5 , A = Int (0 , 1 ) / 5 , x = Int (1 , 1 ) / 2 , ++L, ++R; if (m == 3 ) Sqrt = 3 , A = Int (3 , 1 ) / 6 , x = Int (2 , 1 ), L = (L + 1 ) / 2 , R /= 2 ; if (L > R) return 0 ; pa[0 ] = pb[0 ] = px[0 ] = py[0 ] = 1 ; for (int i = 1 ; i <= k; ++i) { pa[i] = pa[i - 1 ] * A, pb[i] = pb[i - 1 ] * conj (A); px[i] = px[i - 1 ] * x, py[i] = py[i - 1 ] * conj (x); } Int ret = 0 ; for (int i = 0 ; i <= k; ++i) { Int s = 0 ; for (int j = 0 ; j <= i; ++j) s += Sum (L, R, px[j] * py[i - j]) * pa[j] * pb[i - j] * C (i, j); ret += s * stl[k][i]; } return mul (fpow (lgt, P - 2 ), mul (ifac[k], ret.x)); } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif int Ti, m, k; scanf ("%d%d" , &Ti, &m); PreDone (MAXK - 1 ); for (LL L, R; Ti; --Ti) scanf ("%lld%lld%d" , &L, &R, &k), printf ("%d\n" , solve (L, R, k, m)); return 0 ; }
「BJOI2019」送别 题目链接
解题思路送别 $\times$, 带走 $\checkmark$
「BJOI2019」排兵布阵 题目链接
解题思路 这是一道定位在 NOIP Day 1 T2 的送分题 (
考虑 DP. 首先每个城堡和对手完全独立, 互不干扰. 设 $f(i, j)$ 表示已考虑完前 $i$ 个城堡, 小 C 剩余 $j$ 名士兵的得分最大值. 有
其中 $w(i, k)$ 表示对于城堡 $i$ 派遣 $k$ 名士兵, 所有对局的总得分. 显然有 $O(s)$ 的暴力计算方法, 将对手派遣士兵个数从小到大排序, 依次枚举, 可以 $O(1)$ 的时间内转移.
时间复杂度 $O(nms)$.
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <cstdio> #include <algorithm> using namespace std;template <typename T> inline bool ckmax (T& a, const T& b) { return (a < b)? a = b, true : false ; } const int MAXN = 1e2 + 5 , MAXM = 2e4 + 5 ;int s, n, m;int A[MAXN][MAXN], f[MAXN][MAXM];int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif scanf ("%d%d%d" , &s, &n, &m); for (int w, i = 1 ; i <= s; ++i) for (int j = 1 ; j <= n; ++j) scanf ("%d" , &w), A[j][i] = 2 * w + 1 ; int *w, ans = 0 ; for (int i = 1 ; i <= n; ++i) { w = A[i], sort (w + 1 , w + 1 + s); for (int j = 0 ; j <= m; ++j) { for (int k = 0 ; k <= s && j >= w[k]; ++k) ckmax (f[i][j], f[i - 1 ][j - w[k]] + k * i); ckmax (ans, f[i][j]); } } printf ("%d\n" , ans); return 0 ; }
「BJOI2019」光线 题目链接
解题思路 这是一道物理题, 难点在于推式子.
设 $f_i$ 表示前 $i$ 层玻璃, 从第 $1$ 层玻璃射入光的透光率. 那么 $f_n$ 即为答案. 此时问题仍不好解决, 因为没有考虑到反射对答案的影响. 那么设 $g_n$ 表示从第 $n$ 层玻璃向上射入光的反射率. 因此有
表示透过第 $n$ 层的光来自上一层透过的光, 以及反射到上一层, 从而对本层做出贡献的部分.
表示光在第 $n$ 层反射的贡献 ($b_n$), 另外有透过第 $n$ 层 (乘上 $a_n$), 并再次在第 $n - 1$ 层反射回来, 或是多次反射 ($g_{n - 1} \sum\limits_{k = 0} ^ \infty (g_{n - 1} b_n)$), 通过第 $n$ 层玻璃 (在原有基础上乘 $a_n$) 的部分.
递推计算即可. 时间复杂度 $O(n \log P)$.
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <cctype> #include <cstdio> using namespace std;namespace IO { const int MAXSIZE = 1 << 18 | 1 ; char buf[MAXSIZE], *p1, *p2; inline int Gc () { return p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1 , MAXSIZE, stdin), p1 == p2)? EOF: *p1++; } template <typename T> inline void read (T& x) { x = 0 ; int f = 0 , ch = Gc (); while (!isdigit (ch)) f |= ch == '-' , ch = Gc (); while (isdigit (ch)) x = x * 10 + ch - '0' , ch = Gc (); if (f) x = -x; } } using IO::read;const int MAXN = 5e5 + 5 , P = 1e9 + 7 , iv100 = 570000004 ;void exgcd (int a, int b, int & x, int & y) { if (!b) x = 1 , y = 0 ; else exgcd (b, a % b, y, x), y -= a / b * x; } inline int inv (const int & a) { static int x, y; return exgcd (a, P, x, y), (x % P + P) % P; } int n;int A[MAXN], B[MAXN], f[MAXN], g[MAXN];int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif read (n); for (int i = 1 ; i <= n; ++i) { read (A[i]), read (B[i]); A[i] = 1LL * A[i] * iv100 % P, B[i] = 1LL * B[i] * iv100 % P; } f[1 ] = A[1 ], g[1 ] = B[1 ]; for (int i = 2 ; i <= n; ++i) { int iv = inv ((1 - 1LL * B[i] * g[i - 1 ] % P + P) % P); f[i] = 1LL * A[i] * f[i - 1 ] % P * iv % P; g[i] = (B[i] + 1LL * A[i] * A[i] % P * g[i - 1 ] % P * iv % P) % P; } printf ("%d\n" , f[n]); return 0 ; }
「BJOI2019」删数 题目链接
解题思路 可以发现每个数的位置在查询答案时是没有影响的. 如果将所有数丢到一个桶里, 即统计每个数的出现次数, 形成一个柱状的结构. 那么, 对于一次询问, 相当于将所有数向按值域所在位置向左填充, 或者形象地, 将柱子向左推倒. 此时 $[1, n]$ 中未覆盖位置即为答案.
同时要注意最大数 $> n$, 此部分贡献单独统计 —- 因为一定会被修改, 而不能同前文一样计算向左覆盖的位置. 正确性证明大概是先证明此时得出的结果是所有答案的下界, 并将所有重复填充的位置选择一部分修改, 一定能达到这个下界.
现在的问题在于如何维护这个过程, 考虑用线段树去维护这个桶, 即维护每个位置被覆盖的次数.
对于计算答案, 记录区间最小值, 以及最小值出现的次数. 那么, 一个区间能做出贡献, 当且仅当该区间未被覆盖, 即区间最小值为 $0$, 用最小值出现次数更新答案即可. 向上合并时直接将子区间答案合并即可.
对于 $p > 0$, 维护区间加的标记即可. 注意单独统计贡献的部分无需计算覆盖位置, 以及下传标记时需统计答案.
对于 $p = 0$, 也就是全局修改. 对应在桶中的影响, 也就是整体位移. 直接记录当前桶中 $0$ 的位置 $p_0$, 每次对应修改 $p_0$ 即可, 那么查询的区间为 $[p_0 + 1, p_0 + n]$.
值得留意的地方依旧是 $> p_0 + n$ 的位置, 注意加上 / 减去边界的覆盖情况.
时间复杂度 $O(m \log n)$.
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <cctype> #include <cstdio> #include <algorithm> using namespace std;namespace IO { const int MAXSIZE = 1 << 18 | 1 ; char buf[MAXSIZE], *p1, *p2; inline int Gc () { return p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1 , MAXSIZE, stdin), p1 == p2)? EOF: *p1++; } template <typename T> inline void read (T& x) { x = 0 ; int f = 0 , ch = Gc (); while (!isdigit (ch)) f |= ch == '-' , ch = Gc (); while (isdigit (ch)) x = x * 10 + ch - '0' , ch = Gc (); if (f) x = -x; } } using IO::read;const int MAXN = 1.5e5 + 5 , MAXM = MAXN << 2 ;int n, m, p0, N;int A[MAXN], bkt[MAXM];namespace SGT {#define lc (nd << 1) #define rc (nd << 1 | 1) int Mn[MAXM << 2 ], Cnt[MAXM << 2 ], dat[MAXM << 2 ], tag[MAXM << 2 ]; inline void maintain (const int & nd) { dat[nd] = dat[lc] + dat[rc]; Mn[nd] = min (Mn[lc], Mn[rc]); Cnt[nd] = (Mn[lc] == Mn[nd]) * Cnt[lc] + (Mn[rc] == Mn[nd]) * Cnt[rc]; } inline void push (const int & nd, const int & v) { tag[nd] += v, Mn[nd] += v, dat[nd] = (Mn[nd] == 0 ) * Cnt[nd]; } inline void pushdown (const int & nd) { if (tag[nd] != 0 ) push (lc, tag[nd]), push (rc, tag[nd]), tag[nd] = 0 ; } void build (int nd, int L, int R) { if (L == R) return void ( Cnt[nd] = dat[nd] = 1 ); int Mid = (L + R) / 2 ; build (lc, L, Mid), build (rc, Mid + 1 , R); maintain (nd); } void Mdy (int nd, int L, int R, const int & opL, const int & opR, const int & v) { if (opL <= L && R <= opR) return push (nd, v); int Mid = (L + R) / 2 ; pushdown (nd); if (opL <= Mid) Mdy (lc, L, Mid, opL, opR, v); if (opR > Mid) Mdy (rc, Mid + 1 , R, opL, opR, v); maintain (nd); } int Qry (int nd, int L, int R, const int & opL, const int & opR) { if (opL <= L && R <= opR) return dat[nd]; int Mid = (L + R) / 2 ; pushdown (nd); if (opR <= Mid) return Qry (lc, L, Mid, opL, opR); if (opL > Mid) return Qry (rc, Mid + 1 , R, opL, opR); return Qry (lc, L, Mid, opL, opR) + Qry (rc, Mid + 1 , R, opL, opR); } #undef lc #undef rc } inline void Mdy (const int & v, const int & d) { int p = (d > 0 )? v - bkt[v]: v - bkt[v] + 1 ; SGT::Mdy (1 , 1 , N, p, p, d), bkt[v] += d; } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif read (n), read (m); for (int i = 1 ; i <= n; ++i) read (A[i]); N = (m + n) * 2 + 1 , p0 = m + n; SGT::build (1 , 1 , N); for (int i = 1 ; i <= n; ++i) A[i] += p0, Mdy (A[i], 1 ); for (int p, x; m; --m) { read (p), read (x); if (p > 0 ) { if (A[p] <= p0 + n) Mdy (A[p], -1 ); else --bkt[A[p]]; A[p] = p0 + x; if (A[p] <= p0 + n) Mdy (A[p], 1 ); else ++bkt[A[p]]; } else { p = (x < 0 )? (p0 + 1 ) + n: p0 + n; if (bkt[p] > 0 ) SGT::Mdy (1 , 1 , N, p - bkt[p] + 1 , p, -x); p0 -= x; } printf ("%d\n" , SGT::Qry (1 , 1 , N, p0 + 1 , p0 + n)); } return 0 ; }