比赛大图来源:pixiv_id=95319826
出题人:MarisaMagic
题意简述
给定一颗有 n n n 个叶子节点的树,叶子节点层级分别为 k 1 , k 2 , … , k n k_1, k_2, \ldots, k_n k 1 , k 2 , … , k n ,求最少需要多少非叶子节点构成这棵树。
解题思路 (贪心)
保证每个 k k k 层的叶子节点有一个 k − 1 k-1 k − 1 层的父节点即可,因此答案为 max i = 1 n { k i − 1 } \max_{i=1}^{n}{ \{ k_i - 1 \} } max i = 1 n { k i − 1 } 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) void marisa () { int n, ans = 0 ; cin >> n; for (int i = 1 , k; i <= n; i ++ ){ cin >> k; ans = max (ans, k - 1 ); } cout << ans << "\n" ; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
给定一个长宽高为 a , b , c a,b,c a , b , c 的长方体,变大 1 1 1 次会使得边长变为 k k k 倍,求变大 n n n 次后长方体的体积。答案对 1 0 9 + 7 10^9 + 7 1 0 9 + 7 取模。
解题思路 (数学、快速幂)
变大 n n n 次后,长方体体积 V V V 如下:
V = ( a × k n ) × ( b × k n ) × ( c × k n ) = ( a × b × c ) × k 3 n \begin{split}
V &= (a \times k^n) \times (b \times k^n) \times (c \times k^n)
\\\\
&= (a \times b \times c) \times k^{3n}
\end{split}
V = ( a × k n ) × ( b × k n ) × ( c × k n ) = ( a × b × c ) × k 3 n
n n n 比较大,用 快速幂 求出 k 3 n k^{3n} k 3 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 #include <bits/stdc++.h> using namespace std;using ll = long long ;const int mod = 1e9 + 7 ;inline ll qmi (ll a, ll b, int p) { ll res = 1 ; a %= p; while (b){ if (b & 1 ) res = res * a % p; a = a * a % p; b >>= 1 ; } return res; }int main () { ll a, b, c, n, k; cin >> a >> b >> c >> n >> k; ll ans = a * b * c % mod; ans = ans * qmi (k, 3ll * n, mod) % mod; cout << ans << "\n" ; return 0 ; }
出题人:MarisaMagic
题意简述
给定一个 n × m n\times m n × m 的网格图,#
为障碍物,.
可以往相邻的四个位置移动,X
可以往对角的四个位置移动,求从 ( 1 , 1 ) (1,1) ( 1 , 1 ) 到达 ( n , m ) (n,m) ( n , m ) 的最短时间。
解题思路 (BFS)
要求最短时间,从起点 ( 1 , 1 ) (1,1) ( 1 , 1 ) 广度优先搜索(BFS)即可。
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;const int dx[8 ] = {1 , -1 , 0 , 0 , 1 , 1 , -1 , -1 };const int dy[8 ] = {0 , 0 , 1 , -1 , 1 , -1 , 1 , -1 };int main () { int n, m; cin >> n >> m; vector<vector<char >> g (n + 1 , vector <char >(m + 1 )); for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= m; j ++ ) cin >> g[i][j]; auto bfs = [&](){ vector<vector<int >> d (n + 1 , vector <int >(m + 1 , -1 )); queue<pair<int , int >> q; q.emplace (1 , 1 ); d[1 ][1 ] = 0 ; while (q.size ()){ const auto & [x, y] = q.front (); q.pop (); bool c = (g[x][y] == '.' ); for (int k = (c ? 0 : 4 ); k < (c ? 4 : 8 ); k ++ ){ int nx = x + dx[k], ny = y + dy[k]; if (nx < 0 || ny < 0 || nx > n || ny > m) continue ; if (g[nx][ny] == '#' || d[nx][ny] != -1 ) continue ; q.emplace (nx, ny); d[nx][ny] = d[x][y] + 1 ; } } return d[n][m]; }; cout << bfs () << "\n" ; return 0 ; }
出题人:MarisaMagic
题意简述
给定 n n n 个点和 m m m 条边以及 q q q 次询问,每次询问是否可以从 x x x 到达 y y y 。
解题思路 (Floyd、传递闭包)
考虑预处理任意两点之间的可达性,此预处理可以通过 n n n 次搜索 或者 Floyd 求传递闭包 解决。
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> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) void marisa () { int n, m; cin >> n >> m; vector<vector<int >> d (n + 1 , vector <int >(n + 1 , false )); for (int i = 1 , u, v; i <= m; i ++ ){ cin >> u >> v; d[u][v] = true ; } for (int k = 1 ; k <= n; k ++ ) for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) d[i][j] |= d[i][k] & d[k][j]; int q; cin >> q; for (int i = 1 , x, y; i <= q; i ++ ){ cin >> x >> y; cout << (d[x][y] ? "YES" : "NO" ) << "\n" ; } }int main () { int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:MarisaMagic
题意简述
给定 n n n 个数字 r 1 , r 2 , … , r n r_1, r_2, \ldots, r_n r 1 , r 2 , … , r n ,范围 1 ∼ m 1 \sim m 1 ∼ m 。将 1 ∼ m 1 \sim m 1 ∼ m 分成连续的 k k k 段,使得每个数字只属于一段。每个数字按照初始顺序放入每段,分别计算每段的正序对个数并求和。求如何分段使得这个和最小,输出最小值。
解题思路 (动态规划、前缀和)
考虑采用动态规划,定义 f i , k f_{i,k} f i , k :将前 i i i 排分为 k k k 段,以 i i i 作为第 k k k 段结尾的最优方案下的最小值。由此可得状态转移方程如下:
f i , k = min j < i { f j , k − 1 + g e t s ( j + 1 , i ) } f_{i,k} = \min_{j<i} \{ f_{j,k-1} + get_s(j + 1, i) \}
f i , k = j < i min { f j , k − 1 + g e t s ( j + 1 , i )}
其中 j j j 表示枚举上一段(第 k − 1 k - 1 k − 1 段)的结尾;g e t s ( j + 1 , i ) get_s(j + 1, i) g e t s ( j + 1 , i ) 表示将 j + 1 ∼ i j + 1 \sim i j + 1 ∼ i 分为一段,这一段的正序对的个数。动态规划枚举 i , j , k i, j, k i , j , k 的部分时间复杂度为 O ( m 2 k ) \mathcal{O}(m^2k) O ( m 2 k ) 。
关键在于 g e t s ( j + 1 , i ) get_s(j + 1, i) g e t s ( j + 1 , i ) 如何解决。如果每次都遍历一遍整个数组 r 1 , r 2 , … , r n r_1, r_2, \ldots, r_n r 1 , r 2 , … , r n 并判断每个 j + 1 ∼ i j + 1 \sim i j + 1 ∼ i 范围内的数字前面的 j + 1 ∼ i j + 1 \sim i j + 1 ∼ i 排的个数,最优也需要 O ( n ) \mathcal{O}(n) O ( n ) 的时间开销,而我们需要考虑在 O ( 1 ) \mathcal{O}(1) O ( 1 ) 的时间复杂度得到 g e t s ( j + 1 , i ) get_s(j + 1, i) g e t s ( j + 1 , i ) 。
可以采用二维前缀和的思想,定义 s i , j s_{i,j} s i , j :前 i i i 排的前面排数为 1 ∼ j 1 \sim j 1 ∼ j 的个数。我们可以先在 O ( n 2 ) \mathcal{O}(n^2) O ( n 2 ) 的时间复杂度统计出第 i i i 排前面第 j j j 排的数量,然后在 O ( m 2 ) \mathcal{O}(m^2) O ( m 2 ) 的时间复杂度用二维前缀和求出第 1 ∼ i 1 \sim i 1 ∼ i 排前面第 1 ∼ j 1 \sim j 1 ∼ j 排的数量。
对于 g e t s ( j + 1 , i ) get_s(j + 1, i) g e t s ( j + 1 , i ) 其实也就是第 j + 1 ∼ i j + 1 \sim i j + 1 ∼ i 排前面的第 j + 1 ∼ i j + 1 \sim i j + 1 ∼ i 排的数量,故只需要用如下二维前缀和公式求得:
g e t s ( j + 1 , i ) = s [ i ] [ i ] − s [ j ] [ i ] − s [ i ] [ j ] + s [ j ] [ j ] ; get_s(j + 1, i) = s[i][i] - s[j][i] - s[i][j] + s[j][j];
g e t s ( j + 1 , i ) = s [ i ] [ i ] − s [ j ] [ i ] − s [ i ] [ j ] + s [ j ] [ j ] ;
可以自行绘图借助二维前缀和矩阵辅助理解。
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 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) void marisa () { int n, m, K; cin >> n >> m >> K; vector<int > a (n + 1 ) ; vector<vector<int >> s (m + 1 , vector <int >(m + 1 )); for (int i = 1 ; i <= n; i ++ ){ cin >> a[i]; for (int j = 1 ; j < i; j ++ ) s[a[i]][a[j]] += a[j] < a[i]; } for (int i = 1 ; i <= m; i ++ ) for (int j = 1 ; j <= m; j ++ ) s[i][j] += s[i - 1 ][j] + s[i][j - 1 ] - s[i - 1 ][j - 1 ]; auto get_s = [&](int x1, int y1, int x2, int y2){ return s[x2][y2] - s[x1 - 1 ][y2] - s[x2][y1 - 1 ] + s[x1 - 1 ][y1 - 1 ]; }; vector<vector<int >> f (m + 1 , vector <int >(K + 1 , 1e9 )); f[0 ][0 ] = 0 ; for (int i = 1 ; i <= m; i ++ ) for (int j = 0 ; j < i; j ++ ) for (int k = 1 ; k <= min (i, K); k ++ ) f[i][k] = min (f[i][k], f[j][k - 1 ] + get_s (j + 1 , j + 1 , i, i)); cout << f[m][K] << "\n" ; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
出题人:South_Orange
题意简述
给定 n n n 个数,求最小的 x x x 使得前 x x x 个数中选 k k k 个数方差最小值小于等于 T T T
解题思路 (二分、数学)
二分答案位置,然后将该位置前的数组排序(排序后相邻的k k k 个数方差更优),然后遍历一遍,每次 O ( 1 ) O(1) O ( 1 ) 计算新的方差。
然后问题的关键在于怎么 O ( 1 ) O(1) O ( 1 ) 的计算方差。拆分方差的计算公式:
σ 2 = ∑ i = 1 k ( v i − v ˉ ) 2 k = ∑ i = 1 k v i 2 − 2 v ˉ ∑ i = 1 k v i + k v ˉ 2 k \sigma^2 = \frac{\sum_{i=1}^k (v_i - \bar{v})^2}{k} = \frac{\sum_{i=1}^k v_i^2 - 2\bar{v} \sum_{i=1}^k v_i + k\bar{v}^2}{k}
σ 2 = k ∑ i = 1 k ( v i − v ˉ ) 2 = k ∑ i = 1 k v i 2 − 2 v ˉ ∑ i = 1 k v i + k v ˉ 2
对于 ∑ i = 1 k v i 2 \sum_{i=1}^k v_i^2 ∑ i = 1 k v i 2 可以预处理前缀平方和 O ( 1 ) O(1) O ( 1 ) 得到,对于 ∑ i = 1 k v i \sum_{i=1}^k v_i ∑ i = 1 k v i 也是可以预处理前缀和 O ( 1 ) O(1) O ( 1 ) 得到,对于 v ˉ \bar{v} v ˉ 可以通过 ∑ i = 1 k v i k \frac{\sum_{i=1}^k v_i}{k} k ∑ i = 1 k v i 得到。
对于无解的情况用二分的 check 函数特判一下右端点是否可行即可。
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 #include <iostream> using namespace std;#include <vector> #include <algorithm> #include <set> #include <map> #include <queue> #include <cmath> #include <cstring> #include <iomanip> using ll = long long ;const int N = 1e5 + 10 ; ll n, k, t; ll a[N];double b[N], s[N], ss[N];bool check (ll x) { for (int i = 1 ;i <= x;i++) b[i] = a[i]; sort (b + 1 , b + 1 + x); for (int i = 1 ;i <= x;i++) { s[i] = s[i - 1 ] + b[i]; ss[i] = ss[i - 1 ] + b[i] * b[i]; } double fc = 1e18 ; for (int i = k;i <= x;i++) { fc = min (fc, (double )(ss[i] - ss[i - k] - 2 * (s[i] - s[i - k]) * (s[i] - s[i - k]) / k + k * (s[i] - s[i - k]) / k * (s[i] - s[i - k]) / k) / k); if (fc <= t) return true ; } return false ; }void solve () { cin >> n >> k >> t; for (int i = 1 ;i <= n;i++) cin >> a[i]; if (!check (n)) { cout << -1 << '\n' ; return ; } ll l = k, r = n; while (l < r) { ll mid = l + r >> 1 ; if (check (mid)) r = mid; else l = mid + 1 ; } cout << l << '\n' ; }int main () { ios::sync_with_stdio (0 );cin.tie (0 );cout.tie (0 ); int T = 1 ; while (T--) { solve (); } return 0 ; }
出题人:South_Orange
题意简述
给定一个含有n n n 个正整数的数组 { a 1 , a 2 , ⋯ , a n } \{a_1, a_2,\cdots, a_n\} { a 1 , a 2 , ⋯ , a n } ,求有多少个有序四元组 ( i , j , k , l ) (i, j, k, l) ( i , j , k , l ) 满足 a i a_i a i 是 a k a_k a k 的因数且a j a_j a j 是 a l a_l a l 的因数,其中 i , j , k , l i, j, k, l i , j , k , l 互不相等。
解题思路 (容斥原理)
值域在 10 5 {10}^5 10 5 以内,为了方便表示设值域 P = 10 5 P={10}^5 P = 10 5 。先开桶 t x t_x t x 表示 x x x 出现的次数。同时预处理出每个数编号不相等 的因数个数 s x s_x s x 和倍数个数 b x b_x b x ,时间复杂度为调和级数 O ( P ln P ) O(P\ln P) O ( P ln P ) 。
考虑先算出 i ≠ j i\ne j i = j 且 a i ∣ a j a_i\mid a_j a i ∣ a j 的 ( i , j ) (i,j) ( i , j ) 数量 m m m ,枚举较小数 x x x ,得 m = ∑ x = 1 P t x × b x m=\sum_{x=1}^{P}t_x\times b_x m = ∑ x = 1 P t x × b x 。
有了以上问题的答案,平方一下就是 a i ∣ a k , a j ∣ a l a_i\mid a_k,a_j\mid a_l a i ∣ a k , a j ∣ a l 且 i ≠ k , j ≠ l i\ne k,j\ne l i = k , j = l 的 ( i , j , k , l ) (i,j,k,l) ( i , j , k , l ) 组数,还需要容斥减去 i = j , i = l , k = j , k = l i=j,i=l,k=j,k=l i = j , i = l , k = j , k = l 的组数。
首先先减去 满足一个条件的:
i = j i=j i = j ,较小数相等,枚举这个数 x x x ,取两个倍数,组数为 ∑ x = 1 P t x × b x 2 \sum_{x=1}^P t_x\times b_x^2 ∑ x = 1 P t x × b x 2 。
k = l k=l k = l ,较大数相等,枚举这个数 x x x ,取两个因数,组数为 ∑ x = 1 P t x × s x 2 \sum_{x=1}^P t_x\times s_x^2 ∑ x = 1 P t x × s x 2 。
i = l i=l i = l 或 j = k j=k j = k ,即一组中较大数与另一组中较小数相等。枚举相等的中间值 x x x ,取一个因数和一个倍数,组数为 ∑ x = 1 P 2 ( t x × b x × s x ) \sum_{x=1}^P 2(t_x\times b_x\times s_x) ∑ x = 1 P 2 ( t x × b x × s x ) 。
还需要加上 满足两个条件的:
i = j i=j i = j 且 k = l k=l k = l ,此时两个较小数和两个较大数分别相等,组数即为最初求出的 ( i , j ) (i,j) ( i , j ) 数量 m m m 。
i = l i=l i = l 且 j = k j=k j = k ,由于倍数的要求限制了 i ≤ k , j ≤ l i\le k,j\le l i ≤ k , j ≤ l ,所以此时 i , j , k , l i,j,k,l i , j , k , l 均相等,枚举相等值 x x x ,组数为 ∑ x = 1 P t x ( t x − 1 ) \sum_{x=1}^Pt_x(t_x-1) ∑ x = 1 P t x ( t x − 1 ) 。
其他四种情况都会产生 i = k i=k i = k 或 j = l j=l j = l ,与已经确定的条件 i ≠ k , j ≠ l i\ne k,j\ne l i = k , j = l 冲突,所以组数为 0 0 0 。
之后满足三个或四个条件也会产生同样的冲突,组数均为 0 0 0 ,不用处理。
这样就做完了,但是由于 n 4 n^4 n 4 达到了 10 20 {10}^{20} 10 20 级别,开 __int128 才能确保通过。
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 #include <iostream> using namespace std;#include <vector> #include <algorithm> #include <set> #include <map> #include <queue> #include <cmath> #include <cstring> #include <iomanip> using ll = long long ;const int N = 1e5 + 10 ; ll n; ll a[N], cnt[N]; ll b[N], s[N]; __int128 m;void print (__int128 x) { if (x >= 10 ) print (x / 10 ); putchar (x % 10 + '0' ); }void solve () { cin >> n; for (int i = 1 ;i <= n;i++) cin >> a[i], cnt[a[i]]++; for (int i = 1 ;i <= 1e5 ;i++) { for (int j = i;j <= 1e5 ;j += i) { b[i] += cnt[j]; s[j] += cnt[i]; } b[i]--; s[i]--; m += cnt[i] * b[i]; } __int128 ans = m * m; __int128 t = 0 ; for (int i = 1 ;i <= 1e5 ;i++) t += cnt[i] * b[i] * b[i]; ans -= t; t = 0 ; for (int i = 1 ;i <= 1e5 ;i++) t += cnt[i] * s[i] * s[i]; ans -= t; t = 0 ; for (int i = 1 ;i <= 1e5 ;i++) t += 2 * cnt[i] * s[i] * b[i]; ans -= t; ans += m; t = 0 ; for (int i = 1 ;i <= 1e5 ;i++) t += cnt[i] * (cnt[i] - 1 ); ans += t; print (ans); }int main () { ios::sync_with_stdio (0 );cin.tie (0 );cout.tie (0 ); int T = 1 ; while (T--) { solve (); } return 0 ; }