Codeforces Round #818 (Div. 2) A-E题解,即CF1717A-CF1717E,主要是思维,E是数学,这场前五道题的代码是真的简洁。。。。
CF1717A Madoka and Strange Thoughts
题目链接
题意
t t t 组数据。给定正整数 n ∈ [ 1 , 1 0 8 ] n\in[1,10^8] n ∈ [ 1 , 1 0 8 ] ,输出符合条件的整数对 ( a , b ) (a,b) ( a , b ) 的个数,要求 1 ≤ a , b ≤ n , lcm ( a , b ) ≤ 3 gcd ( a , b ) 1\leq a,b \leq n,\text{lcm}(a,b)\leq 3\gcd(a,b) 1 ≤ a , b ≤ n , lcm ( a , b ) ≤ 3 g cd( a , b )
分析
歪解:找规律
将 lcm ( a , b ) = a b gcd ( a , b ) \text{lcm}(a,b)=\frac{ab}{\gcd(a,b)} lcm ( a , b ) = g c d ( a , b ) a b 代入条件改成 3 gcd ( a , b ) 2 ≥ a b 3\gcd(a,b)^2\geq ab 3 g cd( a , b ) 2 ≥ a b ,打表:
1 2 3 4 5 6 7 8 9 10 11 void solve () { int n = 100 ; for (int i = 1 , cnt = 0 ; i <= n; i++) { for (int j = 1 ; j <= i; j++) if (3 * __gcd(i, j) * __gcd(i, j) >= i * j) cnt++; cout << cnt << '\n' ; } }
可以看到规律是答案依次加一个数列 1 , 3 , 3 , 3 , 1 , 5 {1,3,3,3,1,5} 1 , 3 , 3 , 3 , 1 , 5 ,设一个数组就行了:
1 2 3 4 5 6 7 int c[10 ] = {0 , 1 , 4 , 7 , 10 , 11 , 16 };void solve () { int n; cin >> n; cout << n / 6 * c[6 ] + c[n % 6 ] << '\n' ; }
正解
根据数论知识:设a = ρ 1 r 1 ρ 2 r 2 ⋯ ρ k r k , b = ρ 1 s 1 ρ 2 s 2 ⋯ ρ k s k a={\rho_1}^{r_1}{\rho_2}^{r_2}\cdots {\rho_k}^{r_k},b={\rho_1}^{s_1}{\rho_2}^{s_2}\cdots {\rho_k}^{s_k} a = ρ 1 r 1 ρ 2 r 2 ⋯ ρ k r k , b = ρ 1 s 1 ρ 2 s 2 ⋯ ρ k s k ,则:
gcd ( a , b ) = ρ 1 min ( r 1 , s 1 ) ρ 2 min ( r 2 , s 2 ) ⋯ ρ k min ( r k , s k ) lcm ( a , b ) = ρ 1 max ( r 1 , s 1 ) ρ 2 max ( r 2 , s 2 ) ⋯ ρ k max ( r k , s k ) \gcd(a,b)={\rho_1}^{\min(r_1,s_1)}{\rho_2}^{\min(r_2,s_2)}\cdots {\rho_k}^{\min(r_k,s_k)}\\
\text{lcm}(a,b)={\rho_1}^{\max(r_1,s_1)}{\rho_2}^{\max(r_2,s_2)}\cdots {\rho_k}^{\max(r_k,s_k)}
g cd( a , b ) = ρ 1 m i n ( r 1 , s 1 ) ρ 2 m i n ( r 2 , s 2 ) ⋯ ρ k m i n ( r k , s k ) lcm ( a , b ) = ρ 1 m a x ( r 1 , s 1 ) ρ 2 m a x ( r 2 , s 2 ) ⋯ ρ k m a x ( r k , s k )
所以 a , b a,b a , b 的质因子 > 3 > 3 > 3 的次数必须相等,质因子 2 , 3 2,3 2 , 3 可以存在一个次数不等,但次数只能差一个,即 a = 2 b a=2b a = 2 b 或 a = 3 b a=3b a = 3 b ,答案是所有的 ( a , a ) (a,a) ( a , a ) (共 n n n 个)、所有的二倍关系数对(共 2 × ⌊ n 2 ⌋ 2\times \lfloor \frac{n}{2}\rfloor 2 × ⌊ 2 n ⌋ 个)、所有的三倍关系数对(共 2 × ⌊ n 3 ⌋ 2\times \lfloor \frac{n}{3}\rfloor 2 × ⌊ 3 n ⌋ 个)
代码
1 2 3 4 5 6 void solve () { int n; cin>>n; cout << n + n / 2 * 2 + n / 3 * 2 << '\n' ; }
CF1717B Madoka and Underground Competitions
题目链接
题意
t t t 组数据,给一个 n × n n\times n n × n 的方格图,其中坐标 ( r , c ) (r,c) ( r , c ) 的字符是 X
,其余是 .
,要求方格中每连续 k k k 个格子(横竖方向都满足)中必须要出现至少一个 X
,要求给出填X
的方案,使得X
个数尽可能少
分析
不管已填的字符,发现必须按对角线填:
都是每 k k k 个填一个X
,才能最少的情况下满足要求,那么从给定的 ( r , c ) (r,c) ( r , c ) 坐标看,每 k k k 个把这一行填满(溢出回头),每下一行的起始空格 + 1 ( m o d k ) +1(\mod k) + 1 ( m o d k ) ,上一行起始空格 − 1 ( m o d k ) -1(\mod k) − 1 ( m o d k ) ,看代码更容易理解
代码
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 bool mp[N][N];void solve () { int n, k, r, c; cin >> n >> k >> r >> c; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) mp[i][j] = 0 ; int curc = c % k; int cur = curc; for (int i = r; i <= n; i++) { for (int j = cur; j <= n; j += k) if (j) mp[i][j] = 1 ; cur = (cur + 1 ) % k; } cur = curc; for (int i = r - 1 ; i > 0 ; i--) { cur = (cur - 1 + k) % k; for (int j = cur; j <= n; j += k) if (j) mp[i][j] = 1 ; } for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= n; j++) cout << (mp[i][j] ? 'X' : '.' ); cout << '\n' ; } }
题目链接
题意
$ t$ 组数据。给定a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots, a_n a 1 , a 2 , ⋯ , a n 和 b 1 , b 2 , ⋯ , b n b_1,b_2,\cdots, b_n b 1 , b 2 , ⋯ , b n ,试图用操作使 a i = b i a_i=b_i a i = b i 。将数列首尾相连,如果数列 a a a 中左边的数不大于右边的数,它可以一直 + 1 +1 + 1 ,输出是否可以完成变换。
分析
显然,因为没有减法操作,只要有一个 a i > b i a_i>b_i a i > b i 就一定不可能完成
其他情况,这题我写的时候分析复杂了,实际上定点($ a_i=b_i$)没出现之前,这个数列中的每个数都可以尽情上升,一旦出现定点,定点会限制其左边的数列最多呈一个递减 1 1 1 的序列。因此除非本来 a i = b i a_i=b_i a i = b i ,否则 a i a_i a i 最多变换成 b i + 1 + 1 b_{i+1}+1 b i + 1 + 1 ,则要求 b i ≤ b i + 1 + 1 b_i\leq b_{i+1}+1 b i ≤ b i + 1 + 1
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int a[N], b[N];void solve () { int n; cin >> n; bool flag = true ; for (int i = 0 ; i < n; i++) cin >> a[i]; for (int i = 0 ; i < n; i++) { cin >> b[i]; if (b[i] < a[i]) flag = false ; } for (int i = 0 ; i < n && flag; i++) if (b[i] > a[i] && b[i] > b[(i + 1 ) % n] + 1 ) flag = false ; if (flag) cout << "YES" ; else cout << "NO" ; cout << '\n' ; }
CF1717D Madoka and The Corruption Scheme
题目链接
题意
给定一场比赛,有 2 n 2^n 2 n 个参赛者每一轮两两淘汰(树形),我们可以指定每一局左边/右边的参赛者取胜,我们希望获胜的参赛者编号最小。赞助商有 $k $ 次机会可以调整某一局的结果,求不管如何调整能得到的最小的获胜者的编号,1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ m i n ( 2 n − 1 , 1 0 9 ) 1≤n≤10^5,1≤k≤min(2n−1,10^9) 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ m i n ( 2 n − 1 , 1 0 9 )
分析
总共会进行 n n n 轮比赛,如果 k ≥ n k\geq n k ≥ n ,赞助商可以让编号最大的连赢 k k k 轮,答案是 2 n 2^n 2 n ;否则赞助商能改变 k k k 次,那么如果一个人输了 k k k 轮(假设输了一直做修改)后,赞助商也没法改变了,就要找出所有的到赢家的路上会输 k k k 次以上的人,全部给大编号就行了
每个人走到赢家的路上都有 n n n 轮比赛,找出输 $[k+1,n] $ 次的节点个数
证明一个结论:按题意这样的 n n n 层二叉树,输 i i i 次的节点有 C ( n , i ) C(n,i) C ( n , i ) 个(即 n n n 个中选 i i i 个的情况数)
假设 i i i 次成立,下证 i + 1 i+1 i + 1 次的节点个数有 C ( n , i + 1 ) C(n,i+1) C ( n , i + 1 ) 个:
对于到达 i i i 层中的每一条路,连接下面(第 i + 1 i+1 i + 1 层的两个节点)的有一条路是赢一条路是输,包含这条路沿伸一个单位后的所有情况,所以成立
第一层的节点连接两个节点,一个赢一个输,是一轮的所有情况,因此原结论成立
所以答案就是 2 n − ∑ i = k + 1 n C ( n , i ) 2^n-\sum_{i=k+1}^{n}C(n,i) 2 n − ∑ i = k + 1 n C ( n , i )
代码
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 ll qpow (ll m, ll n) { ll ans = 1 ; while (n) { if (n & 1 ) ans = (ans * m) % mod; m = (m * m) % mod; n >>= 1 ; } return ans; } void init (int n) { fac[0 ] = 1 ; for (int i = 1 ; i <= n; i++) fac[i] = 1ll * fac[i - 1 ] * i % mod; inv[n] = qpow (fac[n], mod - 2 ); for (int i = n - 1 ; i >= 0 ; i--) inv[i] = 1ll * inv[i + 1 ] * (i + 1 ) % mod; } ll C (int x, int y) { if (x < y || y < 0 ) return 0 ; return 1ll * fac[x] * inv[x - y] % mod * inv[y] % mod; } void solve () { ll n, k; cin >> n >> k; init (n); ll ans = qpow (2 , n); for (int i = k + 1 ; i <= n; i++) ans = (ans - C (n, i) + mod) % mod; cout << ans << '\n' ; }
CF1717E Madoka and The Best University
题目链接
题意
简洁明了,对于 n ∈ [ 3 , 1 0 5 ] n\in[3,10^5] n ∈ [ 3 , 1 0 5 ] 求 ∑ a + b + c = n lcm ( c , gcd ( a , b ) ) \sum_{a+b+c=n}\text{lcm}(c,\gcd(a,b)) ∑ a + b + c = n lcm ( c , g cd( a , b ) )
分析
枚举两个数O ( n 2 ) O(n^2) O ( n 2 ) 肯定不行,从式子来说尝试枚举 a + b = i a+b=i a + b = i ,则原式= lcm ( n − i , gcd ( a , b ) ) =\text{lcm}(n-i,\gcd(a,b)) = lcm ( n − i , g cd( a , b ) ) ,需要在不到 O ( n ) O(n) O ( n ) 的复杂度情况下找到 gcd ( a , b ) \gcd(a,b) g cd( a , b ) 的所有可能情况和其对应的 ( a , b ) (a,b) ( a , b ) 个数
设 gcd ( a , b ) = j \gcd(a,b)=j g cd( a , b ) = j ,则 a = x j , b = y j , gcd ( x , y ) = 1 , ( x + y ) j = i a=xj,b=yj,\gcd(x,y)=1,(x+y)j=i a = x j , b = y j , g cd( x , y ) = 1 , ( x + y ) j = i ,所以如果反过来枚举 j j j , i i i 就只需枚举 j j j 的倍数,枚举的角度可以把时间复杂度降到 O ( n log n ) O(n\log n) O ( n log n )
gcd ( x , y ) = gcd ( x , i j − x ) = 1 = gcd ( x , i j ) \gcd(x,y)=\gcd(x,\frac{i}{j}-x)=1=\gcd(x,\frac{i}{j}) g cd( x , y ) = g cd( x , j i − x ) = 1 = g cd( x , j i ) ,所以 x x x 的可能取值为 i j \frac{i}{j} j i 的所有质因子,共有 φ ( i j ) \varphi(\frac{i}{j}) φ ( j i ) 个,( i , j ) (i,j) ( i , j ) 的情况下获得的结果是 lcm ( n − i , j ) ∗ φ ( i j ) \text{lcm}(n-i,j)*\varphi(\frac{i}{j}) lcm ( n − i , j ) ∗ φ ( j i ) 个
将上述所有枚举的情况加起来就可以了,枚举的复杂度已经说过,每次要求一个最小公倍数,复杂度粗略记为 O ( log n ) O(\log n) O ( log n ) ,线性筛预处理欧拉函数的复杂度为 O ( n ) O(n) O ( n ) ,则算法的总复杂度为 O ( n log 2 n ) O(n\log^2 n) O ( n log 2 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 bool check[N + 10 ];int phi[N + 10 ];int prime[N + 10 ];int tot; void phi_and_prime_table (int N) { memset (check, false , sizeof (check)); phi[1 ] = 1 ; tot = 0 ; for (int i = 2 ; i <= N; i++) { if (!check[i]) { prime[tot++] = i; phi[i] = i - 1 ; } for (int j = 0 ; j < tot; j++) { if (i * prime[j] > N) break ; check[i * prime[j]] = true ; if (i % prime[j] == 0 ) { phi[i * prime[j]] = phi[i] * prime[j]; break ; } else phi[i * prime[j]] = phi[i] * (prime[j] - 1 ); } } } void solve () { int n; cin >> n; phi_and_prime_table (n); ll ans = 0 ; for (ll j = 1 ; j <= n; j++) for (ll i = j + j; i <= n; i += j) ans = (ans + (n - i) * j / __gcd(n - i, j) * phi[i / j] % mod) % mod; cout << ans << '\n' ; }