总览
退役竞赛选手出来混个存在感,比赛链接,似乎 CSDN 要做成周赛了。
题目难度上比较简单,部分是原题,需要注意些细节。
赛途不允许切出屏幕和使用通讯软件,整体比较像笔试,偏向于考察应试技能,那么有原题也说得过去。
赛前出现长时间无法进入的情况,赛中编辑器和自助评测使用体验较差,例如代码模板对用户不友好(函数参数不一致、头文件配置不灵活等)、语法补全提示相关度忽高忽低、评测结果没有剔除 stderr 等。同天还有力扣周赛和百度之星初赛,它们所在平台在编辑和评测上的用户体验就好些。
题目分析
1. 寻因找祖
题意:求约数个数为 n n n 的最小正整数 x x x, n ≤ 1000 n \leq 1000 n≤1000。
思路:(比较古老的原题,加强版见 SGU 473)
- 令 x = ∏ i = 1 t p i e i x = \prod_{i = 1}^{t}{{p_i}^{e_i}} x=∏i=1tpiei ( p i < p i + 1 , p i is prime ) (p_i < p_{i + 1}, p_i\text{ is prime}) (pi<pi+1,pi is prime),则它的约数个数 σ ( x ) = ∏ i = 1 t ( e i + 1 ) \sigma(x) = \prod_{i = 1}^{t}{(e_i + 1)} σ(x)=∏i=1t(ei+1),因此 e i e_i ei 的范围被限制在 ( e i + 1 ) ∣ n (e_i + 1) | n (ei+1)∣n。
- 如果两个数字的
{
e
i
}
\{e_i\}
{ei} 集合一样,那么它们的约数个数相同。
- 对于一个固定的 { e i } \{e_i\} {ei} 集合,考虑其对应的数字 x x x,如果存在 e i < e i + 1 e_i < e_{i + 1} ei<ei+1,那么可以构造出一个更小的数字 x ′ = ∏ i = 1 t p i e ′ i x' = \prod_{i = 1}^{t}{{p_i}^{{e'}_i}} x′=∏i=1tpie′i 对应该集合,这里 e ′ i = e i + 1 {e'}_i = e_{i + 1} e′i=ei+1, e ′ i + 1 = e i {e'}_{i + 1} = e_i e′i+1=ei。
- 对于一个固定的 { e i } \{e_i\} {ei} 集合,最小的正整数 x x x 一定只用到最小的 t t t 个质数,且 e i ≥ e i + 1 e_i \geq e_{i + 1} ei≥ei+1,否则总能通过调整得到更小的正整数 x x x。
- 接下来可以暴力搜索(可以手动验证状态不多)或者 DP,但考虑到 n is prime n\text{ is prime} n is prime 时答案可能非常大(例如 2 996 2^{996} 2996),不如直接暴力搜索。
代码:Python3
class Solution:
def __init__(self) -> None:
self.pr = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
self.ans = 1
def dfs(self, dep, cur, rem, upp):
if rem == 1:
self.ans = min(self.ans, cur)
return
if dep >= len(self.pr):
return
for i in range(1, upp + 1):
if i * i > rem:
break
if rem % i > 0:
continue
if i > 1:
self.dfs(dep + 1, cur * (self.pr[dep] ** (i - 1)), rem // i, i)
j = rem // i
if i < j: # j > 1
self.dfs(dep + 1, cur * (self.pr[dep] ** (j - 1)), rem // j, j)
def solution(self, n):
self.ans = 10 ** 1000
self.dfs(0, 1, n, n)
return self.ans
2. 通货膨胀-x国货币
题意:构造一个最长的正整数序列 A = [ a 1 , a 2 , … , a m ] A = [a_1, a_2, \ldots, a_m] A=[a1,a2,…,am],使得 a 1 = n a_1 = n a1=n, a i + 1 ∣ a i a_{i + 1} | a_i ai+1∣ai,输出 m m m 的最大值。数据范围好像是 n ≤ 1 0 9 n \leq 10^9 n≤109。
思路:(比较古老的原题)
- 贪心构造即可,使每个 a i a i + 1 \frac{a_i}{a_{i + 1}} ai+1ai 为质数不会使解更差。令 n = ∏ i = 1 t p i e i n = \prod_{i = 1}^{t}{{p_i}^{e_i}} n=∏i=1tpiei ( p i < p i + 1 , p i is prime ) (p_i < p_{i + 1}, p_i\text{ is prime}) (pi<pi+1,pi is prime),则答案为 1 + ∑ i = 1 t e i 1 + \sum_{i = 1}^{t}{e_i} 1+∑i=1tei。
代码:C++
int solution(int n) {
typedef long long LL;
int result = 1;
for(int i = 2; i <= n; ++i) {
if((LL)i * i > n)
i = n;
for( ; n % i == 0; n /= i, ++result);
}
return result;
}
3. 莫名其妙的键盘
题意:初始有一个空串 S S S,每次操作可以向其末尾增加一个小写字母,如果增加的字符是元音字符,则在增加后需要将 S S S 反转,然后才视为此次操作结束。给定目标串 T T T,问有多少种操作方案能使 S S S 在操作结束后变成 T T T。数据范围好像是 ∣ T ∣ ≤ 100 |T| \leq 100 ∣T∣≤100。
思路:
- S S S 在任意时刻必须是 T T T 或 T T T 的反转串的一个区间,那么记录 S S S 变成每个区间的方案即可 DP 出最终答案,时间复杂度 O ( ∣ T ∣ 2 ) \mathcal{O}(|T|^2) O(∣T∣2)。
代码:C++
#include <bits/stdc++.h>
using namespace std;
int solution(std::string str){
int n = str.size();
vector<bool> sp(n);
vector<vector<pair<int, int> > > dp(n, vector<pair<int, int> >(n));
for(int i = 0; i < n; ++i) {
dp[i][i] = {1, 1};
char ch = str[i];
sp[i] = ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
}
for(int len = 2; len <= n; ++len)
for(int L = 0, R = len - 1; R < n; ++L, ++R) {
if(sp[L]) {
dp[L][R].first += dp[L + 1][R].second;
} else {
dp[L][R].second += dp[L + 1][R].second;
}
if(sp[R]) {
dp[L][R].second += dp[L][R - 1].first;
} else {
dp[L][R].first += dp[L][R - 1].first;
}
}
return dp[0][n - 1].first;
}
4. 三而竭
题意:给定正整数 n n n 和 k k k,求最小的正整数 x x x 使得 ∑ i ≥ 0 ⌊ x k i ⌋ ≥ n \sum_{i \geq 0}{\left\lfloor\frac{x}{k^i}\right\rfloor} \geq n ∑i≥0⌊kix⌋≥n。数据范围好像是 n ≤ 1 0 9 n \leq 10^9 n≤109, 2 ≤ k ≤ 10 2 \leq k \leq 10 2≤k≤10。
思路:(比较古老的原题)
- 函数 f ( x ) = ∑ i ≥ 0 ⌊ x k i ⌋ f(x) = \sum_{i \geq 0}{\left\lfloor\frac{x}{k^i}\right\rfloor} f(x)=∑i≥0⌊kix⌋ 单调递增,且 f ( n ) ≥ n f(n) \geq n f(n)≥n,二分即可,时间复杂度 O ( log k n log 2 n ) \mathcal{O}(\log_k{n} \log_2{n}) O(logknlog2n)。
- 也可以做到 O ( log k n ) \mathcal{O}(\log_k{n}) O(logkn),留给读者自行探索。感兴趣可以来做下我出的题目 「LibreOJ β Round #5」最小倍数,提交的代码是公开的。
代码:C++
int solution(std::vector<int>& vec){
int n = vec[0], k = vec[1];
int L = 1, R = n;
while(L < R) {
int M = (L + R) >> 1;
int ctr = 0, rem = M;
for( ; ctr < n && rem > 0; ctr += rem, rem /= k);
if(ctr < n) {
L = M + 1;
} else {
R = M;
}
}
return L;
}
后续
别看了哪有下期啊。