比赛大图来源:pixiv_id=105287025
出题人:MarisaMagic
题意简述
给定一个字符串 S S S ,去除所有字符 3 3 3 输出新的字符串 S ′ S^{'} S ′ 。
解题思路 (语法,字符串基础)
遍历每个字符,遇到 3 3 3 直接忽略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0) void marisa () { string s; cin >> s; for (const auto & ch : s) if (ch != '3' ) cout << ch; }int main () { fastio; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
无限个 2 2 2 和 3 3 3 ,询问正整数 x x x 是否可以表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b 。
解题思路 (数学)
结论 :除 1 1 1 之外的正整数都可以表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b :
当 x = 1 x = 1 x = 1 时,我们无法表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b 的形式。
当正整数 x x x 为偶数时,我们一定可以表示为 2 × x 2 + 3 × 0 2 \times \frac{x}{2} + 3 \times 0 2 × 2 x + 3 × 0 。
当正整数 x x x 为大于 1 1 1 的奇数时,我们一定可以表示为 2 × x − 3 2 + 3 × 1 2 \times \frac{x - 3}{2} + 3 \times 1 2 × 2 x − 3 + 3 × 1 。
以上通过 构造 的方式得出结论,我们也可以采用 数学归纳法 进行证明:
显然,有基础结论:正整数 2 2 2 可以用一个 2 2 2 表示;正整数 3 3 3 可以用一个 3 3 3 表示。
假设 2 ≤ x ≤ k 2 \le x \le k 2 ≤ x ≤ k 的所有正整数都可以被表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b ,试证明 k + 1 k + 1 k + 1 也可以表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b 的形式。
根据假设,k − 1 k - 1 k − 1 表示为 2 × a ′ + 3 × b ′ 2\times a^{'} + 3\times b^{'} 2 × a ′ + 3 × b ′ ,那么 k + 1 k + 1 k + 1 可以表示为 2 × ( a ′ + 1 ) + 3 × b ′ 2\times (a^{'} + 1) + 3\times b^{'} 2 × ( a ′ + 1 ) + 3 × b ′ 。故 k + 1 k + 1 k + 1 也可以表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b 的形式得证。以此类推,任意大于等于 2 2 2 的正整数都可以被表示为 2 × a + 3 × b 2\times a + 3\times b 2 × a + 3 × b 的形式 。
拓展结论 :对于两个 互质 的正整数 x x x 和 y y y ,其最大的不能凑出的整数为 x × y − x − y x \times y - x - y x × y − x − y 。
此拓展结论是一个非常经典的结论,题目可见 P3951 [NOIP 2017 提高组] 小凯的疑惑 。证明可参考 两个互质的正整数不能凑出的最大数的证明 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0) using ll = long long ;int main () { fastio; int T = 1 ; cin >> T; while (T -- ){ ll x; cin >> x; cout << (x == 1 ? "No" : "Yes" ) << "\n" ; } return 0 ; }
出题人:MarisaMagic
题意简述
给定一个 01 01 01 字符串,求最少插入多少个字符使得相邻字符都不同。
解题思路 (双指针、贪心)
对于连续相同的一段 k k k 个字符,只需要插入 k − 1 k - 1 k − 1 个另一种字符即可使得相邻字符都不同。故双指针遍历字符串,维护连续相同一段的前后边界 [ l , r ) [l, r) [ l , r ) ,每一段对答案的贡献为 r − l − 1 r - l - 1 r − l − 1 ,累加答案即可。时间复杂度 O ( n ) \mathcal{O}(n) 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 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0) void marisa () { int n; cin >> n; string s; cin >> s; int l = 0 , r, ans = 0 ; while (l < n){ r = l + 1 ; while (r < n && s[r] == s[l]) r ++ ; ans += r - l - 1 ; l = r; } cout << ans << "\n" ; }int main () { fastio; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
有一个 n × m n\times m n × m 的矩阵 A A A ,已知 A i , j A_{i,j} A i , j 是 i i i 和 j j j 的公倍数,求:∀ x ∈ [ 1 , K ] \forall x \in [1, K] ∀ x ∈ [ 1 , K ] ,矩阵 A A A 最多有多少元素可以是 x x x 。(各结果之间相互独立)
解题思路 (数学、调和级数)
由元素 A i , j A_{i,j} A i , j 是 i i i 和 j j j 的公倍数,可知 i i i 和 j j j 都是 A i , j A_{i,j} A i , j 的约数,即 A i , j m o d i = 0 A_{i,j} \bmod i = 0 A i , j mod i = 0 ,A i , j m o d j = 0 A_{i,j} \bmod j = 0 A i , j mod j = 0 。
记矩阵 A A A 中最多有多少元素可以是 x x x 为 a n s x ans_x an s x 。我们计算行下标 1 ≤ i ≤ n 1 \le i \le n 1 ≤ i ≤ n 中有多少数是 x x x 的约数 和 列下标 1 ≤ j ≤ m 1 \le j \le m 1 ≤ j ≤ m 中有多少数是 x x x 的约数,分别记为 c n t n x cntn_x c n t n x 和 c n t m x cntm_x c n t m x 。由乘法原理可知 a n s x ans_x an s x 等于 c n t n x × c n t m x cntn_x \times cntm_x c n t n x × c n t m x 。
一种 O ( K K ) \mathcal{O}(K\sqrt{K}) O ( K K ) 的 TLE \text{TLE} TLE 做法是:O ( K ) \mathcal{O}(\sqrt{K}) O ( K ) 预处理出每个 x x x (1 ≤ x ≤ K 1 \le x \le K 1 ≤ x ≤ K )的所有约数,对于每个 x x x 其约数存到一个数组中,然后对于每个 x x x 分别二分找到约数序列中第一个大于 n n n 和第一个大于 m m m 的位置,即可求出 c n t n x cntn_x c n t n x 和 c n t m x cntm_x c n t m x 。
考虑换一种思路统计每个 x x x 的约数个数。我们可以枚举约数 i i i ,然后对于每个约数 i i i 枚举其倍数 j = i , 2 × i , 3 × i , … . . . j = i, 2\times i, 3\times i, \ldots... j = i , 2 × i , 3 × i , … ... 直到边界 K K K ,这些 j j j 都有约数 i i i 。因此,我们可以从 1 ≤ i ≤ n 1 \le i \le n 1 ≤ i ≤ n 枚举约数,再枚举其倍数 j j j 更新其约数个数 c n t j cnt_j c n t j ,大致写法如下:
1 2 3 4 5 for (int i = 1 ; i <= n; i ++ ){ for (int j = i; j <= K; j += i){ cnt[j] ++ ; } }
上述过程的时间复杂度为 ∑ i = 1 n K i \sum_{i=1}^{n} \frac{K}{i} ∑ i = 1 n i K ,即 K ∑ i = 1 n 1 i K\sum_{i=1}^{n}\frac{1}{i} K ∑ i = 1 n i 1 。其中 ∑ i = 1 n 1 i \sum_{i=1}^{n} \frac{1}{i} ∑ i = 1 n i 1 的部分为 调和级数 ,调和级数 发散且其增长速度与 ln n \ln n ln n 同阶(证明可参考 调和级数既然是发散的,那么就是无穷大量了,它发散的快吗?什么级别的? )。因此,这种统计每个 x x x 的约数个数的过程时间复杂度为 O ( K ln n ) = O ( K log n ) \mathcal{O}(K\ln n) = \mathcal{O}(K \log n) O ( K ln n ) = O ( K log n ) 。
故我们只需要用此方式分别更新并统计 c n t n cntn c n t n 和 c n t m cntm c n t m ,最后从 1 ≤ x ≤ K 1 \le x \le K 1 ≤ x ≤ K 累加一下答案。时间复杂度为 O ( K log n + K log m ) \mathcal{O}(K\log n + K\log m) O ( K log n + K log m ) 。
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 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0) using ll = long long ;void marisa () { int n, m, K; cin >> n >> m >> K; vector<int > cntn (K + 1 ) , cntm (K + 1 ) ; for (int i = 1 ; i <= n; i ++ ){ for (int j = i; j <= K; j += i){ cntn[j] ++ ; } } for (int i = 1 ; i <= m; i ++ ){ for (int j = i; j <= K; j += i){ cntm[j] ++ ; } } ll ans = 0 ; for (int x = 1 ; x <= K; x ++ ){ ans += (ll)x * cntn[x] * cntm[x]; } cout << ans << "\n" ; }int main () { fastio; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
神子有一棵 n n n 个节点的树,树上的边有开启和关闭两种状态。初始情况下,神子在 1 1 1 号节点,所有的边开启。
每轮的具体环节如下:
神子当前位于节点 u u u 。如果 u u u 度数为 1 1 1 ,那么 神子获胜 。否则,进入第 2 2 2 个环节;
蕾咪会关闭一条树上的边(被关闭的边不会再开启 ),然后进入第 3 3 3 个环节。如果没有剩余的边可以关闭,直接进入第 3 3 3 个环节;
神子选择一条连接 u u u 的 开启 的边,移动到一个相邻的节点上。 如果没有这样的边 ,那么 蕾咪获胜 。 否则,回到第 1 1 1 个环节进行下一轮。
神子和蕾咪都会以最优策略执行每一个环节。判断谁会获胜。
解题思路 (树的遍历,dfs)
自底向上考虑这个问题。从 1 1 1 向下遍历,假设当前走到节点 u u u :
我们可以通过 dfs 遍历整个树,如果当前遍历到的 u u u 是叶子节点或者包含 大于等于 2 2 2 个必胜节点(子树),那么 u u u 也是必胜节点(子树)。最终,若根节点 1 1 1 是必胜节点(子树),那么神子一定获胜;否则,蕾咪一定获胜。时间复杂度 O ( n ) \mathcal{O}(n) 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 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0) void marisa () { int n; cin >> n; vector<vector<int >> e (n + 1 ); vector<int > d (n + 1 ) ; for (int i = 1 , u, v; i < n; i ++ ){ cin >> u >> v; e[u].emplace_back (v); e[v].emplace_back (u); d[u] ++ , d[v] ++ ; } function<bool (int , int )> dfs = [&](int u, int fa){ if (d[u] == 1 ) return true ; int cnt = 0 ; for (const auto & v : e[u]){ if (v == fa) continue ; cnt += dfs (v, u); } return cnt >= 2 ; }; cout << (dfs (1 , 0 ) ? "You win, temporarily." : "Wasted." ) << "\n" ; }int main () { fastio; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
两种画框 a , b a, b a , b ,每种有 n n n 个,每个画框有 p p p 和 h h h 两个属性。第一排放 n n n 个画框 a a a ,属性记为 p a , h a pa, ha p a , ha ;第二排放 n n n 个画框 b b b ,属性记为 p b , h b pb, hb p b , hb ,将这些画框进行重排要求满足:
画框 a a a 从左到右 p a 1 , p a 2 , … , p a n pa_1, pa_2, \ldots, pa_n p a 1 , p a 2 , … , p a n 单调不减 ;
画框 b b b 从左到右 p b 1 , p b 2 , … , p b n pb_1, pb_2, \ldots, pb_n p b 1 , p b 2 , … , p b n 单调不减 ;
∀ i ∈ [ 1 , n ] \forall i \in [1, n] ∀ i ∈ [ 1 , n ] ,有 h a i > h b i ha_i > hb_i h a i > h b i 。
求一种方案输出第一排和第二排的编号排列 或 输出无解。
解题思路 (贪心,模拟)
考虑贪心。对于每一种画框,将价格 p p p 相同的画框划分到一个集合 set
中,集合 set
中存储一对高度和编号 ( h i d , i d ) (h_{id}, id) ( h i d , i d ) 。为了保证价格从低到高,我们可以将价格 p p p 作为键,集合 set
作为值将每种画框存储到 map<int, set<pair<int, int>>>
的数据结构中。存储画框 a a a 的数据结构记为 m p a mp_a m p a ,存储画框 b b b 的数据结构记为 m p b mp_b m p b 。
同时遍历 m p a mp_a m p a 和 m p b mp_b m p b ,从各自最低价格开始。记当前 m p a mp_a m p a 最低价的画框集合为 s t a st_a s t a ,当前 m p b mp_b m p b 最低价的画框集合为 s t b st_b s t b 。
若 s t a st_a s t a 中画框数量 大于等于 s t b st_b s t b 中画框数量:
为了 让后续价格保持单调不减 ,并且 尽可能多地使得后续满足对应位置有 h a i > h b i ha_i > hb_i h a i > h b i ,我们需要为较小数量的 s t b st_b s t b 中的每个画框 ( h b i d , i d ) (hb_{id}, id) ( h b i d , i d ) 在 s t a st_a s t a 中找到一个 最小的严格大于 h b i d hb_{id} h b i d 的画框。
如果可以找到,将对应编号加入到答案中并删除 s t a st_a s t a 中的画框;否则,直接输出无解。若每个 s t b st_b s t b 的画框都可以找到,m p b mp_b m p b 转到下一个价格的画框 b b b 继续进行遍历。特别的,如果 s t a st_a s t a 和 s t b st_b s t b 相等时,s t a st_a s t a 也都分配到一个画框 b b b 上,m p a mp_a m p a 也需要转到下一个价格的画框 a a a 继续进行遍历。
若 s t a st_a s t a 中画框数量 小于 s t b st_b s t b 中画框数量:
此时需要为较小数量的 s t a st_a s t a 中的每个画框 ( h a i d , i d ) (ha_{id}, id) ( h a i d , i d ) 在 s t b st_b s t b 中找到一个 最大的严格小于 h a i d ha_{id} h a i d 的画框。
如果可以找到,将对应编号放入答案并删除 s t b st_b s t b 中的画框;否则,直接输出无解。若每个 s t a st_a s t a 的画框都可以找到,m p a mp_a m p a 转到下一个价格的画框 a a a 继续进行遍历。
不断模拟上述过程,直到最后全部遍历完分别输出编号排列。
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 #include <bits/stdc++.h> using namespace std;#define fastio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) using pii = pair<int , int >;void marisa () { int n; cin >> n; map<int , set<pii>> mpa, mpb; vector<int > pa (n + 1 ) , ha (n + 1 ) ; for (int i = 1 ; i <= n; i ++ ) cin >> pa[i]; for (int i = 1 ; i <= n; i ++ ) cin >> ha[i]; for (int i = 1 ; i <= n; i ++ ){ mpa[pa[i]].insert (pii{ha[i], i}); } vector<int > pb (n + 1 ) , hb (n + 1 ) ; for (int i = 1 ; i <= n; i ++ ) cin >> pb[i]; for (int i = 1 ; i <= n; i ++ ) cin >> hb[i]; for (int i = 1 ; i <= n; i ++ ){ mpb[pb[i]].insert (pii{hb[i], i}); } vector<int > ansa, ansb; auto ita = mpa.begin (), itb = mpb.begin (); while (ita != mpa.end () && itb != mpb.end ()){ auto & [_a, sta] = *ita; auto & [_b, stb] = *itb; if (sta.size () >= stb.size ()){ bool flag = (sta.size () == stb.size ()); for (auto & [x, id] : stb){ auto it = sta.lower_bound (pii{x + 1 , 0 }); if (it == sta.end ()){ cout << "impossible" << "\n" ; return ; } ansa.emplace_back (it -> second); ansb.emplace_back (id); sta.erase (it); } itb ++ ; if (flag) ita ++ ; }else { for (auto & [x, id] : sta){ auto it = stb.lower_bound (pii{x, 0 }); if (it == stb.begin ()){ cout << "impossible" << "\n" ; return ; } it -- ; ansa.emplace_back (id); ansb.emplace_back (it -> second); stb.erase (it); } ita ++ ; } } for (const auto & x : ansa) cout << x << ' ' ; cout << "\n" ; for (const auto & x : ansb) cout << x << ' ' ; cout << "\n" ; }int main () { fastio; int T = 1 ; while (T -- ) marisa (); return 0 ; }