如果说 “我做了一套 AHOI”, 恐怕和 “我做了一套 HNOI” 给人的感觉不太一样…
但其实是一样的? OI 什么时候能实现地域的平衡啊. 这辈子不可能了.
其实不如 SDOI R2 毒瘤 (
「AHOI / HNOI2017」单旋
“邪恶的「卡」带着他的邪恶的「常数」来企图毁灭 H 国.”
可以说是很形象了.
题目链接
解题思路 这是一道性质题.
本题最重要的性质就是, 如果把某个节点 $u$ “spaly” 到根, 那么整棵二叉搜索树的形态不会发生较大改变.
具体地说, 假设当前要把最小值对应节点 $u$ “spaly” 到根, 实际上只做了这几个操作:
$u$ 的右儿子接到 $u$ 父亲 $f$ 的左儿子上.
把 $u$ 设成根, 然后把原来的根接到 $u$ 的右儿子上.
考虑这个过程中的节点深度变化, 可以发现 $u$ 的深度变为 $1$, $u$ 原来的右儿子深度不变, 其余点的深度 $+1$.
那么可以用 BIT 维护这个深度了.
既然关键码互不相同, 那我们直接把关键码离散化后的值视作节点编号, 这样子树内编号一定是连续的. 因为操作的特殊性: 只选择最大值和最小值, 所以直接修改到 1 / n 即可.
再考虑插入操作怎么实现.
注意道每次插入一个节点 $u$, $u$ 的父亲 $f$ 一定是 $u$ 的前驱 / 后继中深度较深的一个, 使用 STL 里的 set
直接维护即可.
时间复杂度 $O(n\log n)$.
感谢这道题让我知道 spaly 多么蠢…
代码实现 代码实现中有一些简单的技巧…
诸如预先在 set
中插入 0
, n+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 <set> #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;typedef set<int >::iterator IT;const int MAXN = 1e5 + 5 ;int nB, m, ans;int Q[MAXN], A[MAXN], B[MAXN];namespace BIT { int C[MAXN]; inline int lowbit (const int & x) { return x & -x; } inline int Qry (const int & pos) { int ret = 0 ; for (int i = pos; i; i -= lowbit (i)) ret += C[i]; return ret; } inline void Mdy (const int & pos, const int & val) { for (int i = pos; i <= nB + 1 ; i += lowbit (i)) C[i] += val; } inline void Mdy (const int & L, const int & R, const int & val) { Mdy (L, val), Mdy (R+1 , -val); } } namespace Spaly { int pre[MAXN], ch[2 ][MAXN], root; set<int > S; inline void init () { S.insert (0 ), S.insert (nB + 1 ); } inline void spaly (int u, int w) { if (u == root) return ; int fa = pre[u]; if (w) BIT::Mdy (1 , fa, 1 ); else BIT::Mdy (fa, nB, 1 ); BIT::Mdy (u, u, 1 - ans); ch[w][fa] = ch[w ^ 1 ][u]; if (ch[w][fa]) pre[ch[w][fa]] = fa; ch[w ^ 1 ][u] = root, pre[root] = u, pre[root = u] = 0 ; } inline void insert (const int & v) { int u = lower_bound (B+1 , B+1 +nB, v) - B; if (!root) root = u, ans = 1 ; else { IT ite = S.lower_bound (u), suf = ite, nxt = --ite; int fa = BIT::Qry (*nxt) > BIT::Qry (*suf)? *nxt: *suf; ans = BIT::Qry (fa) + 1 , pre[u] = fa, ch[fa == *nxt][fa] = u; } S.insert (u), BIT::Mdy (u, u, ans - BIT::Qry (u)); } inline int Nxt (int w) { int u = w? *(++S.rbegin ()): *(++S.begin ()); return ans = BIT::Qry (u), spaly (u, w), u; } inline void Rmv (int w) { int u = Nxt (w); S.erase (u), pre[root = ch[w ^ 1 ][u]] = 0 ; if (w) BIT::Mdy (1 , u, -1 ); else BIT::Mdy (u, nB, -1 ); } } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif read (m); for (int i = 1 ; i <= m; ++i) { read (Q[i]); if (Q[i] == 1 ) read (A[i]), B[++nB] = A[i]; } Spaly::init (); sort (B+1 , B+1 +nB); for (int i = 1 ; i <= m; ++i) { switch (Q[i]) { case 1 : Spaly::insert (A[i]); break ; case 2 : Spaly::Nxt (0 ); break ; case 3 : Spaly::Nxt (1 ); break ; case 4 : Spaly::Rmv (0 ); break ; case 5 : Spaly::Rmv (1 ); break ; default : fprintf (stderr, "ERR\n" ); } printf ("%d\n" , ans); } return 0 ; }
「AHOI / HNOI2017」影魔 题目链接
解题思路 直接统计不好维护, 于是将操作离线, 枚举每个位置, 依次加入贡献 / 差分统计贡献.
记 $c = \max \{ k_s \mid i < s < j \}$, 依次讨论什么情况下对答案会有 $p_1$ / $p_2$ 的贡献.
$i + 1 = j$
此时不存在此 $c$, 对答案有 $p_1$ 的贡献.
$c \le \min \{k_i, k_j \}$
此时有 $p_1$ 的贡献. 但是不好直接统计, 于是转化一下, 考虑某个位置 $i$ 对答案有贡献的情况.
对于每个位置 $i$, 利用单调栈处理出左侧第一个比 $k_i$ 的位置 $L_i$, 以及右侧第一个比 $k_i$ 大的位置 $R_i$. 可以得到
如果一个询问完全包含 $[L_i, R_i]$, 那么对答案有 $p_1$ 的贡献.
$\min \{ k_i, k_j \} < c < \max \{ k_i, k_j \}$
此处有 $p_2$ 的贡献. 沿用 Case 2 的思路. 可以发现
如果一个区间包含 $L_i$, 那么 $L_i$ 可以在 $[i+1, R_i - 1]$ 对答案有 $p_2$ 的贡献.
如果一个区间包含 $R_i$, 那么 $R_i$ 可以在 $[L_i + 1, i-1]$ 对答案有 $p_2$ 的贡献.
这样就做完了. 实际代码实现并不复杂…
考虑到拆成扫描线之后修改 / 查询的操作总和达到了 $10^6$ 的级别…
于是使用 BIT 实现区间加 / 区间求和, 时间复杂度 $O(n \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 #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;typedef long long LL;const int MAXN = 2e5 + 5 , MAXM = 5 * MAXN;struct Ask { int type, x, L, R, val; bool operator < (const Ask& rhs) const { return x < rhs.x || (x == rhs.x && type < rhs.type); } } Q[MAXM]; int n, m, qidx;int A[MAXN], p1, p2;int Lpos[MAXN], Rpos[MAXN], stk[MAXN], top;LL Ans[MAXN]; namespace BIT { LL C1[MAXN], C2[MAXN]; inline int lowbit (const int & x) { return x & -x; } inline void Mdy (const int & pos, const int & val) { for (int i = pos; i <= n; i += lowbit (i)) C1[i] += val, C2[i] += 1LL * val * pos; } inline void Mdy (const int & L, const int & R, const int & val) { Mdy (L, val), Mdy (R+1 , -val); } inline LL Qry (const int & pos) { LL ret = 0 ; for (int i = pos; i; i -= lowbit (i)) ret += C1[i] * (pos + 1 ) - C2[i]; return ret; } inline LL Qry (const int & L, const int & R) { return Qry (R) - Qry (L-1 ); } } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif read (n), read (m), read (p1), read (p2); for (int i = 1 ; i <= n; ++i) read (A[i]); stk[top = 0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { while (top && A[stk[top]] < A[i]) Rpos[stk[top--]] = i; Lpos[i] = stk[top], stk[++top] = i; } while (top) Rpos[stk[top--]] = n + 1 ; for (int i = 1 ; i <= n; ++i) { if (i < n) Q[++qidx] = (Ask){ 0 , i + 1 , i, i, p1 }; if (Lpos[i] >= 1 && Rpos[i] <= n) Q[++qidx] = (Ask){ 0 , Rpos[i], Lpos[i], Lpos[i], p1 }; if (Lpos[i] >= 1 && i + 1 <= Rpos[i] - 1 ) Q[++qidx] = (Ask){ 0 , Lpos[i], i + 1 , Rpos[i] - 1 , p2 }; if (Rpos[i] <= n + 1 && Lpos[i] + 1 <= i - 1 ) Q[++qidx] = (Ask){ 0 , Rpos[i], Lpos[i] + 1 , i - 1 , p2 }; } for (int L, R, i = 1 ; i <= m; ++i) { read (L), read (R); Q[++qidx] = (Ask){ i, L-1 , L, R, -1 }; Q[++qidx] = (Ask){ i, R, L, R, 1 }; } sort (Q+1 , Q+1 +qidx); for (int i = 1 ; i <= qidx; ++i) { if (Q[i].type == 0 ) BIT::Mdy (Q[i].L, Q[i].R, Q[i].val); else Ans[Q[i].type] += Q[i].val * BIT::Qry (Q[i].L, Q[i].R); } for (int i = 1 ; i <= m; ++i) printf ("%lld\n" , Ans[i]); return 0 ; }
「AHOI / HNOI2017」礼物 当年入门多项式的时候写的题…
题目链接
解题思路 首先旋转的操作可以看作是在枚举一个起始点 $k$, 超出部分复制一遍 $x_i$ 接到 $x_n$ 后面即可. 把差异值形式地写下来就是
展开, 得
除了最后面的和式, 其余部分直接统计 / 二次函数极值就可以解决.
考虑构造一个序列 $t_i = x_{n-i+1}$, 那么
这就是一个卷积的形式了, 使用 FFT / NTT 优化这一过程即可.
时间复杂度 $O(n \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 #include <cmath> #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 << 18 | 1 ;const int P = 998244353 , G = 3 , iG = 332748118 ;int fpow (int base, int b) { int ret = 1 ; while (b > 0 ) { if (b & 1 ) ret = 1LL * ret * base % P; base = 1LL * base * base % P, b >>= 1 ; } return ret; } namespace Poly { int r[MAXN]; inline void init (const int & Lim, const int & L) { for (int i = 1 ; i < Lim; ++i) r[i] = (r[i>>1 ] >> 1 ) | ((i & 1 ) << (L-1 )); } void NTT (int * f, const int & Lim, const int & type) { for (int i = 1 ; i < Lim; ++i) if (i < r[i]) swap (f[i], f[r[i]]); for (int Mid = 1 ; Mid < Lim; Mid <<= 1 ) { int unit = fpow (type > 0 ? G: iG, (P - 1 ) / (Mid << 1 )); for (int i = 0 ; i < Lim; i += Mid << 1 ) { int w = 1 ; for (int j = 0 ; j < Mid; ++j, w = 1LL * w * unit % P) { int f0 = f[i+j], f1 = 1LL * w * f[i+j+Mid] % P; f[i+j] = (f0 + f1) % P, f[i+j+Mid] = (f0 - f1 + P) % P; } } } if (type < 0 ) { int inv = fpow (Lim, P-2 ); for (int i = 0 ; i < Lim; ++i) f[i] = 1LL * f[i] * inv % P; } } } int n, m, ans;int a[MAXN], b[MAXN], t[MAXN];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]); for (int i = 1 ; i <= n; ++i) read (b[i]); int B = 0 ; for (int i = 1 ; i <= n; ++i) ans += a[i] * a[i] + b[i] * b[i], B += a[i] - b[i]; int c1 = floor (-1.0 * B / n), c2 = ceil (-1.0 * B / n); ans += min (n * c1 * c1 + 2 * B * c1, n * c2 * c2 + 2 * B * c2); for (int i = 1 ; i <= n; ++i) t[n + i] = t[i] = a[n - i + 1 ]; int Lim = 1 , L = 0 ; while (Lim <= 2 *n + n) Lim <<= 1 , ++L; Poly::init (Lim, L), Poly::NTT (t, Lim, 1 ), Poly::NTT (b, Lim, 1 ); for (int i = 0 ; i < Lim; ++i) t[i] = 1LL * t[i] * b[i] % P; Poly::NTT (t, Lim, -1 ); int p2 = -P; for (int i = n + 1 ; i <= 2 * n; ++i) p2 = max (p2, 2 * t[i]); printf ("%d\n" , ans - p2); return 0 ; }
「AHOI / HNOI2017」大佬 很真实的题面.
题目链接
解题思路 首先有一个朴素的 DP, 把能丢到状态的都丢进去…
考虑到打倒大佬可以分为两部分, 也就是保证自己存活, 以及摧毁大佬自信.
注意到 “保证存活” 这一限制和每天采用什么策略有关, 而 “摧毁自信” 只和总天数有关.
于是可以 DP 出保证存活的条件下最大斗争天数.
设 $f(i, j)$ 表示第 $i$ 天, 自信值为 $j$ 时用于打倒大佬的最大天数. 则有转移
表示这天选择同大佬斗争.
表示这天去做水题 续命…
在得到最大斗争天数 $md$ 后, 直接 BFS 计算这些天内能积累的的讽刺能力 $F$, 以及所用天数 $D$.
那么对于一个自信值为 $C$ 的大佬, 依次枚举 “怼大佬” 的次数, 即 0, 1, 2, 并判定即可.
对于使用两次 “怼大佬” 操作的情况, 利用 Two-Pointer 的技巧配合 $F$ 的单调性枚举即可.
具体地说, 当前合法的两次操作 $i, j$ 满足 $F_i + F_j \le C$, 且 $C - F_i - F_j + D_i + D_j \le md$.
(换言之, 不能直接把大佬自信值嘲讽到负, 且剩余部分可以还嘴解决)
这种带搜索的题目, 时间复杂度不想分析了, $O(\texttt{能过})$.
代码实现
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 #include <map> #include <queue> #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;typedef pair<int , int > Pii;const int MAXN = 105 , MAXM = 1e6 + 5 , MAXC = 1e8 ;int n, m, mc, md;int A[MAXN], W[MAXN];int f[MAXN][MAXN];int F[MAXM], D[MAXM], nF;map<int , int > M; map<Pii, bool > vis; void BFS () { static int QF[MAXM], QL[MAXM], Qd[MAXM]; int head = 1 , tail = 0 ; QF[++tail] = 1 , QL[tail] = 0 , Qd[tail] = 1 ; while (head <= tail) { int fe = QF[head], l = QL[head], d = Qd[head]; ++head; if (!M.count (fe)) M[fe] = d; if (d >= md || 1LL * fe * l > MAXC) continue ; if (!vis[Pii (l+1 , fe)]) { QF[++tail] = fe, QL[tail] = l + 1 , Qd[tail] = d + 1 ; vis[Pii (l+1 , fe)] = true ; } if (1 < l && 1LL * fe * l <= MAXC && !vis[Pii (l, fe * l)]) { QF[++tail] = fe * l, QL[tail] = l, Qd[tail] = d + 1 ; vis[Pii (l, fe * l)] = true ; } } } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif read (n), read (m), read (mc); for (int i = 1 ; i <= n; ++i) read (A[i]); for (int i = 1 ; i <= n; ++i) read (W[i]); for (int i = 1 ; i <= n; ++i) for (int j = A[i]; j <= mc; ++j) { f[i][j - A[i]] = max (f[i][j - A[i]], f[i-1 ][j] + 1 ); int p = min (mc, j - A[i] + W[i]); f[i][p] = max (f[i][p], f[i-1 ][j]); } for (int i = 1 ; i <= n; ++i) for (int j = 0 ; j <= mc; ++j) md = max (md, f[i][j]); BFS (); for (auto ite = M.begin (); ite != M.end (); ++ite) F[++nF] = ite->first, D[nF] = ite->second; while (m--) { static int C, ans; read (C), ans = C <= md; for (int i = 1 ; i <= nF && !ans; ++i) ans |= F[i] <= C && D[i] + C - F[i] <= md; for (int j = 1 , i = nF; i && !ans; --i) while (!ans && j <= nF && F[i] + F[j] <= C) ans |= D[i] + D[j] + C - F[i] - F[j] <= md, ++j; printf ("%d\n" , ans); } return 0 ; }
「AHOI / HNOI2017」队长快跑 这是一道 计算几何 nan
题, 至少当年是这样.
题目链接
解题思路 似乎这道题 6-10 的数据是错的, 嘿嘿.
于是去学习了某一种广为流传的做法, 希望做法不假.
首先有一步转化, 每一个机关, 都可以根据其对起点 $S$ 到终点 $T$ 的影响, 看作倾角为 $\frac{\pi}{2}$ 或 $\frac{\pi}{2}$ 的两条射线.
感觉不画图是不会明白的, 那么祭上 lb2003 的题解 吧
此时的情况就比较少了, 利用两个单调队列, 维护一条合法路径即可. 由维护路径的过程可以得知, 此时得到的答案一定是最优的.
时间复杂度 $O(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 #include <cmath> #include <cstdio> #include <cassert> #include <algorithm> using namespace std;typedef long long LL;const int MAXN = 1e6 + 5 ;namespace Geo { const double INFD = 1e18 ; struct Vector { LL x, y; Vector (LL _x = 0 , LL _y = 0 ): x (_x), y (_y) { } Vector operator + (const Vector& rhs) const { return Vector (x + rhs.x, y + rhs.y); } Vector operator - (const Vector& rhs) const { return Vector (x - rhs.x, y - rhs.y); } }; typedef Vector Point; inline LL Dot (const Vector& A, const Vector& B) { return A.x * B.x + A.y * B.y; } inline LL Cross (const Vector& A, const Vector& B) { return A.x * B.y - A.y * B.x; } inline double Length (const Vector& A) { return sqrt (Dot (A, A)); } inline double angle (const Vector& v) { return atan2 (v.y, v.x); } } using namespace Geo;inline void read (Point& p) { scanf ("%lld%lld" , &p.x, &p.y); }int n, nA;double theta[MAXN];Point A[MAXN], P[MAXN], S, T; int Q[2 ][MAXN], head[2 ], tail[2 ];int pre[MAXN], idx[MAXN], t[MAXN], type[MAXN]; inline bool cmp (const int & x, const int & y) { return P[x].x < P[y].x; } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif scanf ("%d" , &n), read (T); for (int i = 1 ; i <= n; ++i) read (P[i]), scanf ("%lf" , theta + i); S = Point (0 , 0 ); for (int i = 1 ; i <= n; ++i) { double angl = angle (S - P[i]), angr = angle (T - P[i]); if (angl > angr) type[i] = !(angr < theta[i] && theta[i] < angl); else type[i] = angl < theta[i] && theta[i] < angr; } for (int i = 1 ; i <= n; ++i) idx[i] = i; sort (idx+1 , idx+1 +n, cmp); A[++nA] = S; for (int i = 1 ; i <= n && P[idx[i]].x < T.x; ++i) if (S.x < P[idx[i]].x) A[++nA] = P[idx[i]], t[nA] = type[idx[i]]; A[++nA] = T; for (int d = 0 ; d < 2 ; ++d) Q[d][head[d] = tail[d] = 1 ] = 1 ; for (int i = 2 ; i <= nA; ++i) { int d = t[i], sign = (d > 0 ? 1 : -1 ); int &head0 = head[d], &tail0 = tail[d], *Q0 = Q[d]; int &head1 = head[d^1 ], &tail1 = tail[d^1 ], *Q1 = Q[d^1 ]; if (head1 < tail1 && Cross (A[i] - A[Q1[head1]], A[Q1[head1+1 ]] - A[Q1[head1]]) * sign <= 0 ) { while (head1 < tail1 && Cross (A[i] - A[Q1[head1]], A[Q1[head1+1 ]] - A[Q1[head1]]) * sign <= 0 ) ++head1; pre[i] = Q1[head1]; head0 = ++tail0, Q0[tail0] = Q1[head1]; } else { while (head0 < tail0 && Cross (A[i] - A[Q0[tail0-1 ]], A[Q0[tail0]] - A[Q0[tail0-1 ]]) * sign <= 0 ) --tail0; pre[i] = Q0[tail0]; } Q0[++tail0] = i; } double ans = 0.0 ; for (int i = nA; i != 1 ; i = pre[i]) ans += Length (A[i] - A[pre[i]]), assert (i != 0 ); printf ("%.10lf\n" , ans); return 0 ; }
「AHOI / HNOI2017」抛硬币 题目链接
解题思路 这是一道组合数学题. 分 $a = b$ 和 $a > b$ 两种情况讨论.
$a = b$
先考虑所有情况数, 即 $2 ^ {a + b}$.
如果把硬币全部翻转, 最后输赢的结果却不一定翻转. 所以此时要统计 $A$ 获胜的情况, 需要减去翻转前后 $A$ 都输给 $B$, 也就是平局的方案数. 那么答案为
考虑右侧和式的组合意义, 即两次在 $a$ 个物品中选出 $i$ 个. 等价于一次选出 $i$ 个, 另一次选出 $a-i$ 个. 也就是在 $2a$ 个物品中选出 $a$ 个. 即
式子中有个 / $2$, 但 $2$ 在这些个模数下不存在逆元, 于是可以认为模数为 $2 \times 10^k$, 最后直接将结果 / $2$ 即可.
但是这样有些… 考虑 帕斯卡法则 , 有
而 $\binom{2a-1}{a-1} = \binom{2a-1}{a}$, 所以答案为
从而避开了 / $2$ 的操作.
$a > b$
还是从翻转硬币的角度入手, 这次要考虑翻转前后 $A$ 都获胜的情况. 那么答案为
改变枚举顺序, 得
稍微整理一下, 得
利用上一情况的处理方法处理掉 / $2$ 即可. (如果把和式处理掉, 那就是后文的优化 3 了)
推出来式子之后直接抄了一份鲁棒性良好的拓展卢卡斯的板子上去, 喜获第一个点跑 10s 的好成绩.
接下来的问题在于如何卡常…
考虑到模数一定形如 $10^k$, 也就是质因数仅包含 $2$ 和 $5$.
那么枚举质因数的过程可以省略, 中国剩余定理的过程也可以简化不少.
在能用 int
的情况下, 避免使用 long long
.
在现代评测机下效果并不显著…
考虑到组合数具有对称性, 计算组合数只需要算一半即可, 常数减半.
具体实现的时候, 需要讨论 $a + b$ 奇偶性来避免少加一项的麻烦.
预处理一个 “伪阶乘”, 即在阶乘中除去 $2$ / $5$ 的倍数, 用于优化拓展 Lucas 计算.
此处优化最为明显.
还有一个小地方就是 $2^9 = 512$, 远小于 $5^9 = 1953125$, 开在一起对缓存不友好…
不过可以简化代码 doge
代码实现
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 #include <cstdio> typedef long long LL;const int MAXL = 25 , MAXN = 2e6 + 5 ;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 & n, const int & m) { static int x, y; return exgcd (n % m, m, x, y), (x % m + m) % m; } int fpow (LL base, LL b, int m) { int ret = 1 ; while (b > 0 ) { if (b & 1 ) ret = ret * base % m; base = base * base % m, b >>= 1 ; } return ret; } char Ctl[MAXL];int M[MAXL], pow2[MAXL], pow5[MAXL], fac[2 ][MAXN];int calc (LL n, int d, int p) { if (!n) return 1 ; int s = fpow (fac[d == 5 ][p - 1 ], n / p, p); return 1LL * s * fac[d == 5 ][n % p] % p * calc (n / d, d, p) % p; } int MultiLucas (LL n, LL m, int d, int p, int k) { int c = 0 ; for (LL i = n; i; i /= d) c += i / d; for (LL i = m; i; i /= d) c -= i / d; for (LL i = n-m; i; i /= d) c -= i / d; if (c >= k) return 0 ; return 1LL * fpow (d, c, p) * calc (n, d, p) % p * inv (calc (m, d, p), p) % p * inv (calc (n-m, d, p), p) % p; } int ExLucas (LL n, LL m, int k) { const int &fact1 = pow2[k], &fact2 = pow5[k]; int a1 = MultiLucas (n, m, 2 , fact1, k), a2 = MultiLucas (n, m, 5 , fact2, k); int ret = 1LL * inv (fact2, fact1) * fact2 % M[k] * a1 % M[k]; ret = (ret + 1LL * inv (fact1, fact2) * fact1 % M[k] * a2 % M[k]) % M[k]; return ret; } int main () {#ifndef ONLINE_JUDGE freopen ("input.in" , "r" , stdin); #endif fac[0 ][0 ] = fac[1 ][0 ] = 1 ; for (int i = 1 ; i < 512 ; ++i) fac[0 ][i] = (i % 2 == 0 )? fac[0 ][i-1 ]: 1LL * i * fac[0 ][i-1 ] % 512 ; for (int i = 1 ; i < 1953125 ; ++i) fac[1 ][i] = (i % 5 == 0 )? fac[1 ][i-1 ]: 1LL * i * fac[1 ][i-1 ] % 1953125 ; pow2[0 ] = pow5[0 ] = M[0 ] = 1 ; for (int i = 1 ; i <= 9 ; ++i) M[i] = M[i-1 ] * 10 , pow2[i] = pow2[i-1 ] * 2 , pow5[i] = pow5[i-1 ] * 5 ; LL a, b; int K; while (scanf ("%lld%lld%d" , &a, &b, &K) != EOF) { int Mod = M[K], ans = fpow (2 , a + b - 1 , Mod); if (a == b) ans = (ans - ExLucas (a + a - 1 , a - 1 , K) + Mod) % Mod; else { for (LL i = (a + b) / 2 + 1 ; i < a; ++i) ans = (ans + ExLucas (a + b, i, K)) % Mod; if ((a + b) % 2 == 0 ) ans = (ans + ExLucas (a + b - 1 , (a + b) / 2 , K)) % Mod; } sprintf (Ctl, "%%0%dd\n" , K), printf (Ctl, ans); } return 0 ; }