E. CCPC String
1. 题意
给定一个字符串,含有 C | P | ?,问最多能组成多少个 CCPC,也可以是 CCCCPCC,等。
2. 思路
发现核心限制条件其实是 P 的位置,所以重点在判断两个 P 之间的距离。在字符串前后各加一个 P 作为哨兵,然后二分查询每个不是 C 的前后继 P 即可。总贡献值为 m i n ( l e f t / 2 , r i g h t ) min( left / 2, right ) min ( l e f t /2 , r i g h t ) 。
3. 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const int maxn = 2e5 + 5 ;void solve ( ) { string s; cin >> s; s = 'p' + s + 'p' ; int n = s.length ( ); vector< int > pos ; for ( int i = 0 ; i < n; ++ i ) if ( s[i] == 'p' ) pos.push_back ( i ); ll ans = 0 ; for ( int i = 1 ; i < n - 1 ; ++ i ) { if ( s[i] == 'c' ) continue ; int p1 = lower_bound ( pos.begin ( ), pos.end ( ), i ) - pos.begin ( ) - 1 ; int p2 = upper_bound ( pos.begin ( ), pos.end ( ), i ) - pos.begin ( ); int left = abs ( i - pos[p1] ) - 1 , right = abs ( i - pos[p2] ) - 1 ; ans += min ( left / 2 , right ); } cout << ans << '\n' ; }
F. Chase Game 3
1. 题意
在一个 n n n 个点的图上,有两条链:L 1 L_1 L 1 是标准的 1 ↔ 2 ↔ ⋯ ↔ n 1 \leftrightarrow 2 \leftrightarrow \dots \leftrightarrow n 1 ↔ 2 ↔ ⋯ ↔ n ;L 2 L_2 L 2 是按排列顺序连接的 p 1 ↔ p 2 ↔ ⋯ ↔ p n p_1 \leftrightarrow p_2 \leftrightarrow \dots \leftrightarrow p_n p 1 ↔ p 2 ↔ ⋯ ↔ p n 。 Q 在 L 1 L_1 L 1 上走,C 在 L 2 L_2 L 2 上走。两人轮流移动(或原地不动)。
问: 是否对于任意 的初始位置,C 都存在必胜策略能在有限步内与 Q 相遇?
2. 思路
把样例的图画出来,然后计算两个相邻下标点之间,L1 和 L2 两条链的距离。注意到,L1 的距离为 1,然后如果存在 L2 的距离为 3 及以上的,都是不可能的情况。大胆猜测这就是正解,考虑验证,的确可行,如果距离大于 2,Q 就可以在两个点之间来回横跳,而 C 需要更多的时间去追赶。
3. 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const int maxn = 4e5 + 5 ;int p[maxn], n;void solve ( ) { cin >> n; map<int , int > mp; for ( int i = 1 ; i <= n; ++ i ) { cin >> p[i]; mp[p[i]]=i; } for ( int i = 1 ; i <= n-1 ; ++ i ) { if ( abs ( mp[i] - mp[i+1 ] ) > 2 ) { cout << "No" << '\n' ; return ; } } cout << "Yes" << '\n' ; }
L. Completely Multiplicative Function
1. 题意
要求构造一个长度为 n n n 的 ( 1 , − 1 ) (1, -1) ( 1 , − 1 ) 序列,满足下列要求:
f ( x ) = { − 1 , 1 } f(x) = \{-1, 1\} f ( x ) = { − 1 , 1 } 。
f ( x ) f ( y ) = f ( x y ) f(x)f(y) = f(xy) f ( x ) f ( y ) = f ( x y ) 。
f ( 1 ) + f ( 2 ) + ⋯ + f ( n ) = k f(1) + f(2) + \cdots + f(n) = k f ( 1 ) + f ( 2 ) + ⋯ + f ( n ) = k 。
2. 思路
这个 f ( x ) f ( y ) = f ( x y ) f(x)f(y) = f(xy) f ( x ) f ( y ) = f ( x y ) ,下意识联想到数论中的积性函数,然后我就想到欧拉函数,然后就跑偏了。大胆猜测贪心,− 1 -1 − 1 的位置大概率和质数有关,假设我修改 k k k 为 − 1 -1 − 1 ,那么 k 3 , k 5 , … k^3, k^5, \dots k 3 , k 5 , … 都是 − 1 -1 − 1 ,同时所有包括质因数 k k k 的奇数次幂的数也会被反转。
然后分析题意,什么情况下是不可行的,假设有 a a a 个 1,b b b 个 − 1 -1 − 1 ,就可以得到下列方程,a − b = k , a + b = n a - b = k, a + b = n a − b = k , a + b = n ,求解得到 a = n + k 2 , b = n − k 2 a = \frac{n+k}{2}, b = \frac{n-k}{2} a = 2 n + k , b = 2 n − k ,发现如果 n n n 和 k k k 的奇偶性不同,无法得到整数的 a a a 和 b b b ,此时不可能构造。
接着考虑如何贪心反转,从小到大反转,还是从大到小反转。注意到 > n 2 > \frac{n}{2} > 2 n 的质数的贡献值一定为 1,因为最小的质数是 2,乘起来会超过 n n n ;而 ≤ n 2 \leq \frac{n}{2} ≤ 2 n 的质数,数字越小,贡献值越大。答案呼之欲出,从小到大贪心,然后用 > n 2 > \frac{n}{2} > 2 n 的质数补充剩余空隙。
注意特判最后 − 1 -1 − 1 的数量是不是等于 b b b ,如果不等于,也是不可以构造的。
3. 代码
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 const int maxn = 1e6 + 5 ;int minp[maxn]; vector<ll> primes;void sieve ( ) { for ( ll i = 2 ; i < maxn; ++ i ) { if ( minp[i] == 0 ) { minp[i] = i; primes.push_back ( i ); } for ( ll p : primes ) { if ( p > minp[i] || i * p >= maxn ) break ; minp[i * p] = p; } } }int ans[maxn], n, k;void solve ( ) { cin >> n >> k; if ( n % 2 != k % 2 || k > n ) { cout << -1 << '\n' ; return ; } int need = ( n - k ) / 2 ; for ( int i = 1 ; i <= n; ++ i ) ans[i] = 1 ; int cnt = 0 ; int sz = primes.size ( ); for ( int p : primes ) { if ( p > n / 2 || p > n ) break ; if ( cnt == need ) break ; int delta = 0 ; for ( ll pp = p; pp <= n; ) { for ( ll i = pp; i <= n; i += pp ) { if ( (i / pp) % p != 0 ) { if ( ans[i] == 1 ) delta ++; else delta --; } } if ( n / p / p < pp ) break ; pp *= (p * p); } if ( delta > 0 && cnt + delta <= need ) { cnt += delta; for ( ll pp = p; pp <= n; ) { for ( ll i = pp; i <= n; i += pp ) { if ( (i / pp) % p != 0 ) ans[i] *= -1 ; } if ( n / p / p < pp ) break ; pp *= (p * p); } } } if ( cnt < need ) { for ( ll p : primes ) { if ( p <= n / 2 ) continue ; if ( p > n ) break ; if ( cnt == need ) break ; ans[p] = -1 ; cnt ++; } } if ( cnt != need ) { cout << -1 << '\n' ; } else { for ( int i = 1 ; i <= n; ++ i ) cout << ans[i] << " " ; cout << '\n' ; } }
J. Best Carry Player 3
1. 题意
给定 X , Y , K X, Y, K X , Y , K ,每次可以进行下列三种操作,问至少几次可以把 X X X 转化为 Y Y Y 。X X X 加一;X X X 减一;X X X 异或 [ 0 , k ] [0,k] [ 0 , k ] 中的一个数。
2. 思路
题目问最小操作次数,首选 BFS 暴力,显然不会这么简单,那就需要研究异或(位运算)的性质了。然后发现,关键点在于这个异或上,也就是说,在 k k k 最高位及其以下的比特位,都是可以通过异或来进行修改的,但是更高位的就只能通过 + 1 +1 + 1 来处理了。
接着就可以把给定的 X X X 划分为两块,记 L L L 是大于 k k k 的最小 2 的幂,也就是 2 m + 1 2^{m+1} 2 m + 1 ,(m m m 是 k k k 的最高位数)那么任何数字 N N N 都可以写成 N = H ⋅ L + R N = H \cdot L + R N = H ⋅ L + R ,H H H 是高位的部分,而 R R R 是低位的部分。
接着处理低位的情况,在一个块的内部,从 a a a 到 b b b 的最少步数 d i s t ( a , b ) dist(a, b) d i s t ( a , b ) 为以下三种情况:0 步:a = b a = b a = b ;1 步:∣ a − b ∣ = 1 |a - b| = 1 ∣ a − b ∣ = 1 或者 ( a ⊕ b ) ≤ k (a \oplus b) \leq k ( a ⊕ b ) ≤ k ;2 步:剩余情况。
然后贪心,如果 X , Y X, Y X , Y 不在同一块内,就需要先让 X X X 的地位变成 L − 1 L-1 L − 1 ,然后再通过 + 1 +1 + 1 的操作来进行进位。如果中间隔着很多完整的块,每个块的代价都是固定的。需要从 0 0 0 走到 L − 1 L-1 L − 1 ,再 + 1 +1 + 1 ,单块代价:w = d i s t ( 0 , L − 1 ) + 1 w = dist(0, L-1) + 1 w = d i s t ( 0 , L − 1 ) + 1 ,总代价就是 ( H y − H x − 1 ) ⋅ w (H_{y} - H_{x} - 1) \cdot w ( H y − H x − 1 ) ⋅ w 。最后补齐 X X X 的低位即可:d i s t ( 0 , Y % L ) dist(0, Y \% L) d i s t ( 0 , Y % L ) 。
3. 代码
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> using namespace std;using ll = long long ;const int maxn = 2e5 + 5 ;int dist ( ll x, ll y, ll k ) { if ( x == y ) return 0 ; if ( abs (x - y) <= 1 || (x ^ y) <= k ) return 1 ; else return 2 ; }void solve ( ) { ll x, y, k; cin >> x >> y >> k; if ( x > y ) swap ( x, y ); if ( x == y ) { cout << 0 << '\n' ; return ; } if ( k <= 1 ) { cout << abs ( x - y ) << '\n' ; return ; } int msb = (int )log2 ( k ); ll L = 1ll << ( msb + 1 ); ll Hx = x / L, Rx = x % L; ll Hy = y / L, Ry = y % L; if ( Hx == Hy ) { cout << dist ( Rx, Ry, k ) << '\n' ; return ; } else { ll ans = 0 ; ans += dist ( Rx, L-1 , k ) + 1 ; if ( Hy > Hx + 1 ) { ll w = dist ( 0 , L-1 , k ) + 1 ; ans += (Hy - Hx - 1 ) * w; } ans += dist (0 , Ry, k); cout << ans << '\n' ; } }int main ( ) { cin.tie ( 0 )->sync_with_stdio ( 0 ); int _t = 1 ; cin >> _t ; while ( _t -- ) solve ( ); return 0 ; }
C. DFS Order 3
1. 题意
给定一棵 n n n 个节点的树,分别从 1 → n 1 \to n 1 → n 点开始的 DFS 序,要求构造出原树。
2. 思路
本题需要用到两个 DFS 的定理(当然我赛前不知道,没写出来)。
DFS 序中最后一个点一定是叶子节点。
DFS 序中第二个点一定与第一个点直接相连。
因为保证存在一颗满足条件的树,不妨假设 1 为根节点,然后枚举叶子节点,从 1 出发的 DFS 序中,从后往前删点,同时添加边即可。
注意细节,既然 v v v 是叶子节点,那么根据引理1 它在整棵树中只有一个邻居,接着根据 引理2 以 v v v 为根节点的 DFS,D[v][2] 一定就是它的相邻点。
3. 代码
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 const int maxn = 1000 + 5 ;int D[maxn][maxn], n;int ptr[maxn];bool del[maxn];void solve ( ) { cin >> n; for ( int i = 1 ; i <= n; ++ i ) del[i] = 0 , ptr[i] = 2 ; for ( int i = 1 ; i <= n; ++ i ) { for ( int j = 1 ; j <= n; ++ j ) { cin >> D[i][j]; } } int rt = 1 , lf = n; vector< pair<int , int > > edges; for ( int k = 0 ; k < n - 1 ; ++ k ) { while ( del[ D[rt][lf] ] ) lf --; int v = D[rt][lf]; while ( del[ D[v][ptr[v]] ] ) ptr[v] ++; int u = D[v][ptr[v]]; edges.push_back ({ u, v }); del[v] = 1 ; } for ( auto [u, v] : edges ) cout << u << " " << v << '\n' ; }