第十五届蓝桥杯软件赛国赛C++ B组 题解。
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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { string s = "kfdhtshmrw4nxg#f44ehlbn33ccto#mwfn2waebry#3qd1ubwyhcyuavuajb#vyecsycuzsmwp31ipzah#catatja3kaqbcss2th" ; int res = 0 ; for (int i = 0 ; i < s.size (); i++){ for (int j = 8 ; j <= 16 && i + j - 1 < s.size (); j++){ string tmp = s.substr (i, j); int dig = 0 ; int chr = 0 ; for (auto &i : tmp){ dig += isdigit (i); chr += ('a' <= i && i <= 'z' ) || ('A' <= i && i <= 'Z' ); } res += (dig && dig + chr != j); } } cout << res; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
B. 蚂蚁开会 由于题目提到:不保证任意两条线段之间交点有限,所以求交点是肯定不可行的。
观察数据范围,不妨直接标记每条线段所经过的整点,在所有被标记过的整点中寻找被标记次数大于 1 的即可。
标记线段经过的整点时,可以将线段分为 “两个方向长度的gcd” 个段,这样每个段的两个端点都是整点,且只有这些端点是整点。
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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { int n; cin >> n; unordered_map<int , unordered_map<int , int >> mp; for (int i = 0 ; i < n; i++){ int a, b, c, d; cin >> a >> b >> c >> d; int g = gcd (c - a, d - b); int x = a, y = b; int dx = (c - a) / g, dy = (d - b) / g; mp[x][y] ++; do { x += dx, y += dy; mp[x][y] ++; } while (!(x == c && y == d)); } int res = 0 ; for (auto &[_, l] : mp){ for (auto &[_, cnt] : l){ res += cnt > 1 ; } } cout << res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
C. 选数概率 $$ \frac{ab}{C(a+b+c,2)}=\frac{517}{2091},\frac{bc}{C(a+b+c,2)}=\frac{2632}{10455},\frac{ab}{C(a+b+c,2)}=\frac{308}{2091} $$
于是不难得到: $$ ab:bc:ab=517\times 5:2632:308\times5 $$ 进而不难得到: $$ a:b:c=ab\times ac:ab\times bc:ac\times bc=55:94:56 $$ 先要求 $a+b+c$ 最小值,直接令 $a=55,b=94,c=56$ 即可。
(这里可以验证一下是否满足给出的等式,同时不难发现,由于 $ab$,$C(a+b+c,2)$ 都是二次的,因此其值只与 $a,b,c$ 比例有关,如果验证成功,那么这样取值就是最小的了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { int i = 517 * 5 , j = 2632 , k = 308 * 5 ; int a = i * k, b = i * j, c = j * k; int g = gcd (gcd (a, b), c); a /= g, b /= g, c /= g; if (2091 * a * b != 517 * (a + b + c) * (a + b + c - 1 ) / 2 ){ cout << "No" << '\n' ; return ; } cout << a << ',' << b << ',' << c << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
D. 立定跳远 增加检查点 $=$ 在检查点间多跳跃一次
使用爆发技能 $=$ 在检查点间多跳跃一次
所以其实爆发技能和一个检查点的效果是等价的,也就是说,相当于现在我们可以多跳跃 $m+1$ 次。
接下来二分单次跳跃的距离 $L$,看我们需要多跳跃的次数是否在 $m+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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { int n, m; cin >> n >> m; m++; vector<int > a (n + 1 ) ; for (int i = 1 ; i <= n; i++) cin >> a[i]; auto check = [&](int x){ int cnt = 0 ; for (int i = 1 ; i <= n; i++){ if (a[i] == a[i - 1 ]) continue ; cnt += (a[i] - a[i - 1 ] + x - 1 ) / x - 1 ; } return cnt <= m; }; int l = 0 , r = a[n] - a[0 ]; while (l + 1 != r){ int mid = l + r >> 1 ; if (check (mid)) r = mid; else l = mid; } cout << r << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
E. 最小字符串 贪心插入显然是最优的。
我们可以先将插入串做一个排序,然后用两个指针分别指向被插入串和插入串。
只要这里选择插入串中的字符是更优的,就选择插入串,否则选择原串。
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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { int n, m; cin >> n >> m; string a, b; cin >> a >> b; sort (b.begin (), b.end ()); for (int i = 0 , j = 0 ; i < n; i++){ while (j != m && b[j] < a[i]){ cout << b[j++]; } cout << a[i]; if (i == n - 1 ){ while (j != m){ cout << b[j++]; } } } } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
F. 数位翻转 由于数据范围比较小,可以直接考虑处理出翻转后的序列 $b$,然后进行一个 $O(n^2)$ 的 $dp$ 来选择哪些段进行翻转。
处理翻转就先得到二进制 01 串然后反着倒回十进制就好了。
$dp[i][j][0/1]$:表示前 i 个字符,至此翻转过 j 个段,第 i 个字符(不翻转/翻转)。
转移就很简单了: $$ dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]) + a[i]\ dp[i][j][1] = max(dp[i - 1][j - 1][0], dp[i - 1][j][1]) + b[i] $$ 最后在 $dp[n][j][0/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 #include <bits/stdc++.h> #define int long long using namespace std;constexpr int inf = 1e18 ;void solve () { int n, m; cin >> n >> m; vector<int > a (n + 1 ) , b (n + 1 ) ; for (int i = 1 ; i <= n; i++){ int x; cin >> x; a[i] = x; string s; while (x){ s += (x & 1 ) + '0' ; x >>= 1 ; } for (auto &c : s){ b[i] <<= 1 ; b[i] += c - '0' ; } } vector<vector<vector<int >>> dp (n + 1 , vector<vector<int >>(m + 1 , vector <int >(2 , -inf))); dp[0 ][0 ][0 ] = 0 ; for (int i = 1 ; i <= n; i++){ dp[i][0 ][0 ] = dp[i - 1 ][0 ][0 ] + a[i]; for (int j = 1 ; j <= m; j++){ dp[i][j][0 ] = max (dp[i - 1 ][j][0 ], dp[i - 1 ][j][1 ]) + a[i]; dp[i][j][1 ] = max (dp[i - 1 ][j - 1 ][0 ], dp[i - 1 ][j][1 ]) + b[i]; } } int res = 0 ; for (int j = 0 ; j <= m; j++){ res = max (res, dp[n][j][0 ]); res = max (res, dp[n][j][1 ]); } cout << res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
G. 数星星 一个 $|V_G|$ 的星星,就是在中间的点选择了它的 $|V_G|-1$ 的邻点。
那么其实图中有用的信息就只有度数,我们把每个点的度数都记录下来。
那么最后的答案就是: $$ \sum_u\sum_{k=L}^R C_{d[u]}^{k-1} $$ 这个直接计算显然复杂度大了,不妨考虑一点记忆化,处理过 $d[u]$ 的值,就记录下 $\sum_{k=L}^R C_{d[u]}^{k-1}$ 的值,避免重复计算,而不同的 $d[u]$ 值肯定是不多的,这样处理就能有效控制时间复杂度了。
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 #include <bits/stdc++.h> #define int long long using namespace std;constexpr int mod = 1e9 + 7 ;constexpr int N = 1e5 + 1 ;int qmi (int a, int k, int p) { int res = 1 ; while (k){ if (k & 1 ) res = res * a % p; a = a * a % p; k >>= 1 ; } return res; } int inv (int a) { return qmi (a, mod - 2 , mod); } vector<int > fact (N) ;void init () { fact[0 ] = 1 ; for (int i = 1 ; i < N; i++){ fact[i] = fact[i - 1 ] * i % mod; } } int C (int a, int b) { if (a < b) return 0 ; return fact[a] * inv (fact[b] * fact[a - b] % mod) % mod; } void solve () { int n; cin >> n; vector<int > d (n) ; for (int i = 1 ; i < n; i++){ int u, v; cin >> u >> v; u--, v--; d[u] ++, d[v] ++; } int L, R; cin >> L >> R; int res = 0 ; unordered_map<int , int > mp; for (int i = 0 ; i < n; i++){ if (mp.count (d[i])){ res += mp[d[i]]; continue ; } int sum = 0 ; for (int j = L; j <= R; j++){ sum += C (d[i], j - 1 ); sum %= mod; } res += (mp[d[i]] = sum); res %= mod; } cout << res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); init (); solve (); return 0 ; }
H. 套手镯 我们可以先考虑一维的情况:
给出一堆区间,找到一个框框 $[L,R]$ 使得完全包含的区间最多。
将区间按右端点排列,然后用一个优先队列保存左端点,每次加入一个新区间,将左端点不能满足的全部弹出即可。这个过程中,优先队列大小的最大值就是框框能包含的最多区间数了。
那么对于这个二维的情况,我们一维一维的处理就好了,先在 $y$ 轴上按上述手段处理一遍,然后提取出所有在当前无限长框框($y=down,y=up$)中的圆,再在 $x$ 轴上处理一遍,就能求出一次最多能包含的圆的个数了。
注意最后要把 $w,h$ 反过来再做一遍。
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 #include <bits/stdc++.h> #define int long long using namespace std;struct cir { int x, y, r; bool operator > (const cir &o) const { return y - r > o.y - o.r; } }; struct seg { int l, r; seg (int l = 0 , int r = 0 ) : l (l), r (r) {} bool operator > (const seg &o) const { return l > o.l; } }; void solve () { int n, w, h; cin >> n >> w >> h; vector<cir> p (n) ; for (int i = 0 ; i < n; i++){ int x, y, r; cin >> x >> y >> r; p[i] = {x, y, r}; } sort (p.begin (), p.end (), [](const cir &a, const cir &b){ return a.y + a.r < b.y + b.r; }); int res = 0 ; auto dodo = [&]{ priority_queue<cir, vector<cir>, greater<cir>> Q; for (int i = 0 ; i < n; i++){ Q.push (p[i]); auto u = Q.top (); while (p[i].y + p[i].r - h > u.y - u.r){ Q.pop (); if (Q.empty ()) break ; u = Q.top (); } if (Q.empty ()) continue ; auto tmp = Q; vector<seg> segs; while (!tmp.empty ()){ auto v = tmp.top (); tmp.pop (); segs.emplace_back (v.x - v.r, v.x + v.r); } sort (segs.begin (), segs.end (), [](const seg &a, const seg &b){ return a.r < b.r; }); priority_queue<seg, vector<seg>, greater<seg>> S; for (int j = 0 ; j < segs.size (); j++){ S.push (segs[j]); auto s = S.top (); while (segs[j].r - w > s.l){ S.pop (); if (S.empty ()) break ; s = S.top (); } int sz = S.size (); res = max (res, sz); } } }; dodo (); swap (w, h); dodo (); cout << res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
I. 跳石头 不难发现: $$ S_x=c_x\cup S_{x+c_x}\cup S_{2x} $$ 因此每一个集合所包含的权值都能由后面的集合转移而来,我们逆序处理一遍,就能得到所有集合了。
对于这种转移,用 $bitset$ 可以非常高效的实现(或运算)。
统计集合的最大元素个数即可。
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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { int n; cin >> n; vector<int > c (n + 1 ) ; for (int i = 1 ; i <= n; i++){ cin >> c[i]; } int res = 0 ; vector<bitset<40001>> uni (n + 1 ); for (int i = n; i >= 1 ; i--){ uni[i][c[i]] = 1 ; if (i + c[i] <= n) uni[i] |= uni[i + c[i]]; if (i + i <= n) uni[i] |= uni[i + i]; res = max (res, (int )uni[i].count ()); } cout << res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }
J. 最长回文前后缀 第一步,找出当前就存在的公共前后缀,统计个数后删去。
对于剩下的部分,我们一定是删去某个前缀或某个后缀(否则就没有回文前后缀了),然后考虑得到最长的回文前后缀。
这一部分可以用 KMP 实现:
先考虑删去后缀,对一个字符串 $s = c_1c_2\cdots c_n$,我们希望找到一个 $c_1c_2…=c_rc_{r-1}…$ 的最长回文前后缀。我们记 $t$ 是 $s$ 的逆序。
做拼接:str=s+#+t
,这样,我们只需要利用 KMP 求出 str 中的 “最长相同前后缀” 即可,实现是 $O(n)$ 的。
再详细一些:str = c_1c_2...c_n#c_nc_{n-1}...c_1
,在 $str$ 中 字符 #
后的最长相同前后缀,就等价于 $s$ 的前缀 与 删去一段后缀后的回文后缀。
同样的,考虑删除前缀的话,就做拼接 str=t+#+s
就可以。
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 #include <bits/stdc++.h> #define int long long using namespace std;void solve () { string s; cin >> s; int cnt = 0 ; { int i = 0 , j = s.size () - 1 ; while (s[i] == s[j]){ i++, j--, cnt++; } s = s.substr (i, s.size () - 2 * cnt); } auto get_f = [](string s){ int n = s.size (); vector<int > f (n + 1 ) ; for (int i = 1 , j = 0 ; i < n; i++){ while (j && s[i] != s[j]){ j = f[j]; } j += (s[i] == s[j]); f[i + 1 ] = j; } return f; }; int res = 0 ; auto dodo = [&](string a, string b){ auto f = get_f (a + "#" + b); for (int i = a.size () + 2 ; i < f.size (); i++){ if (i + f[i] < f.size ()) res = max (res, f[i]); } }; string t = s; reverse (t.begin (), t.end ()); dodo (s, t); dodo (t, s); cout << cnt + res << '\n' ; } signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); solve (); return 0 ; }