SDNU ACM-ICPC 2020 Extra Training Contest 14 训练赛 A B C D G K 题解

tech2026-04-22  2

A A water problem

一.题目大意

\quad n n n 是否同时为 73 73 73 137 137 137 的倍数.

\quad l e n g t h ( n ) ≤ 1 0 7 length(n) \leq 10^7 length(n)107.

二.分析

\quad 赛时产生失智行为,水题直接写.

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)1e7; const int N = (int)3e2; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = (ll)1e9 + 7; const double eps = 1e-6; char s[M + 5]; int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); int ca = 0; while(~scanf("%s", s + 1)) { printf("Case #%d: ", ++ca); int n = strlen(s + 1); int r = 0; for(int i = 1; i <= n; ++i) r = (r * 10 + s[i] - '0') % 10001; puts(r ? "NO" : "YES"); } return 0; }

B Zhu and 772002

一.题目大意

\quad 给出 n n n 个数 a [ 1.. n ] a[1..n] a[1..n],其中 a [ 1.. n ] a[1..n] a[1..n] 的最大质因数 ≤ 2 × 1 0 3 \leq 2\times 10^3 2×103.

\quad 每个数至多选一次,求有多少种选数方案使得选出来数的乘积为完全平方数.

\quad n ≤ 3 × 1 0 2 , a [ i ] ≤ 1 0 18 n \leq 3 \times 10^2, a[i] \leq 10^{18} n3×102,a[i]1018.

二.分析

\quad 对每一个质因子列一个方程 ∑ j = 1 n A i , j x j = 0    ( m o d    2 ) \begin{aligned} \sum_{j=1}^n A_{i,j}x_{j} = 0 \; ( mod \; 2) \end{aligned} j=1nAi,jxj=0(mod2).

\quad 上式中, A i , j A_{i, j} Ai,j 表示 a [ j ] a[j] a[j] 含有第 i i i 个质数的指数的奇偶性, x j x_j xj 表示 a [ j ] a[j] a[j] 选不选.

\quad 由此,得到异或方程组,用高斯消元求出矩阵的秩 r r r。由于 x j ∈ { 0 , 1 } x_j \in \{0, 1\} xj{0,1},所以答案为 2 n − r − 1 2^{n-r} - 1 2nr1

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)2e3; const int N = (int)3e2; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = (ll)1e9 + 7; const double eps = 1e-6; int cnt, prime[M + 5]; bool is_prime[M + 5]; bool a[N + 5][N + 5]; void init() { cnt = 0; memset(is_prime, 1, sizeof(is_prime)); is_prime[0] = is_prime[1] = 0; for(int i = 2; i <= M; ++i) { if(is_prime[i]) prime[++cnt] = i; for(int j = 1; j <= cnt && i * prime[j] <= M; ++j) { is_prime[i * prime[j]] = 0; if(i % prime[j] == 0) break; } } } void divide(int p, ll n) { for(int i = 1; i <= cnt; ++i) { if(n % prime[i] == 0) { int c = 0; ll t = n; while(t % prime[i] == 0) ++c, t /= prime[i]; a[i][p] = (c & 1); } } } int gauss(int n, int m) { int c, r; for(c = 1, r = 1; c <= m; ++c) { int t = r; for(int i = r; i <= n; ++i) { if(fabs(a[i][c]) > fabs(a[t][c])) t = i; } if(fabs(a[t][c]) < eps) continue; for(int i = c; i <= m + 1; ++i) swap(a[t][i], a[r][i]); for(int i = m + 1; i >= c; --i) a[r][i] /= a[r][c]; for(int i = 1; i <= n; ++i) { if(i == r) continue; if(fabs(a[i][c]) > eps) { for(int j = m + 1; j >= c; --j) { a[i][j] -= a[r][j] * a[i][c]; } } } ++r; } for(int i = r; i <= n; ++i) { if(fabs(a[i][m + 1]) > eps) return -1;//无解 } return m - r + 1;//自由元个数 } ll quick(ll a, ll b, ll p = mod) { ll s = 1; while(b) { if(b & 1) s = s * a % p; a = a * a % p; b >>= 1; } return s; } void work() { memset(a, 0, sizeof(a)); int n; scanf("%d", &n); ll x; for(int i = 1; i <= n; ++i) scanf("%lld", &x), divide(i, x); int r = gauss(cnt, n); printf("%lld\n", ((quick(2, r) - 1) % mod + mod) % mod); } int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); init(); int T; scanf("%d", &T); for(int ca = 1; ca <= T; ++ca) { printf("Case #%d:\n", ca); work(); } return 0; }

C Magic boy Bi Luo with his excited tree

一.题目大意

\quad T T T 组测试,每次给一颗 n n n 个点的无根树,每个点有相应的价值 V [ i ] V[i] V[i],每条边有相应的花费 C [ i ] C[i] C[i].

\quad 当以 i i i 号点为根时,树的权值定义为从 i i i 号点出发,{价值和 - 花费和} 的最大值.

\quad 注意:每个点的价值至多获得一次,每条边的花费可多次.

\quad 求当根 i i i [ 1 , n ] [1,n] [1,n] 所有值时对应的树的权值.

\quad T ≤ 1 0 4 , n ≤ 1 0 5 , ∑ n < 1 0 6 , 1 ≤ V [ i ] , C [ i ] ≤ 1 0 4 T \leq 10^4, n \leq 10^5, \sum n < 10^6, 1 \leq V[i], C[i] \leq 10^4 T104,n105,n<106,1V[i],C[i]104.

二.分析

\quad 很明显的换根 DP,但还是写了很久…

\quad f [ i ] [ 0 ] f[i][0] f[i][0] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的最大收益.

\quad f [ i ] [ 1 ] f[i][1] f[i][1] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点为 i i i 的最大收益.

\quad f [ i ] [ 2 ] f[i][2] f[i][2] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的次大收益.

\quad p [ i ] p[i] p[i] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的最大收益对应的子节点编号.

\quad g [ i ] [ 0 ] g[i][0] g[i][0] 表示在以 1 1 1 号点为根的前提下,从 i i i 号点往 f a [ i ] fa[i] fa[i] 的方向走,终点不为 i i i 的最大收益.

\quad g [ i ] [ 1 ] g[i][1] g[i][1] 表示在以 1 1 1 号点为根的前提下,从 i i i 号点往 f a [ i ] fa[i] fa[i] 的方向走,终点为 i i i 的最大收益.

\quad 上述状态表示乍一看很晕,但其实画画图分析一下就自然而然地写出来了.

\quad 详见代码.

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)1e5; const int N = (int)25; const int inf = 0x3f3f3f3f; const ll mod = (ll)772002; const double eps = 1e-6; int n, cnt; int head[M + 5]; struct node { int v, w, nx; }Edge[M * 2 + 5]; int V[M + 5]; int f[M + 5][3]; int p[M + 5]; int g[M + 5][2]; void init() { cnt = 0; for(int i = 1; i <= n; ++i) { head[i] = -1; } } void add(int u, int v, int w) { Edge[cnt].v = v; Edge[cnt].w = w; Edge[cnt].nx = head[u]; head[u] = cnt++; } void dfs1(int u, int fa) { f[u][0] = V[u], f[u][1] = V[u], f[u][2] = -inf; int f0, f1; for(int i = head[u]; ~i; i = Edge[i].nx) { int v = Edge[i].v; if(v == fa) continue; dfs1(v, u); f0 = max(0, f[v][0] - Edge[i].w); f1 = max(0, f[v][1] - 2 * Edge[i].w); if(f[u][0] + f1 < f[u][1] + f0) f[u][0] = f[u][1] + f0, p[u] = v; else f[u][0] = f[u][0] + f1; f[u][1] += f1; } for(int i = head[u]; ~i; i = Edge[i].nx) { int v = Edge[i].v; if(v == fa || v == p[u]) continue; f0 = max(0, f[v][0] - Edge[i].w); f1 = max(0, f[v][1] - 2 * Edge[i].w); f[u][2] = max(f[u][2], f[u][1] - f1 + f0); } } void dfs2(int u, int fa) { int f1; for(int i = head[u]; ~i; i = Edge[i].nx) { int v = Edge[i].v; if(v == fa) continue; g[v][0] = V[v]; f1 = max(0, f[v][1] - 2 * Edge[i].w); g[v][0] = max(g[v][0], V[v] + g[u][0] - V[u] + f[u][1] - f1 - Edge[i].w); if(p[u] != v) g[v][0] = max(g[v][0], V[v] + g[u][1] - V[u] + f[u][0] - f1 - Edge[i].w); else g[v][0] = max(g[v][0], V[v] + g[u][1] - V[u] + f[u][2] - f1 - Edge[i].w); g[v][1] = V[v] + max(0, g[u][1] - V[u] + f[u][1] - f1 - 2 * Edge[i].w); dfs2(v, u); } } void work() { scanf("%d", &n); init(); for(int i = 1; i <= n; ++i) scanf("%d", &V[i]); for(int i = 1, u, v, w; i <= n - 1; ++i) scanf("%d %d %d", &u, &v, &w), add(u, v, w), add(v, u, w); dfs1(1, 0); g[1][0] = V[1], g[1][1] = V[1]; dfs2(1, 0); for(int i = 1; i <= n; ++i) printf("%d\n", max(f[i][0] + g[i][1], f[i][1] + g[i][0]) - V[i]); } int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); int T; scanf("%d", &T); for(int ca = 1; ca <= T; ++ca) { printf("Case #%d:\n", ca); work(); } return 0; }

D Danganronpa

一.题目大意

\quad n n n 种礼物,每种物品有 a [ i ] 个 a[i] 个 a[i].

\quad 学生们排成一行,老师给每位学生发两个礼物,分别为普通礼物和神秘礼物,要求相邻两位同学的普通礼物不相同,神秘礼物没有限制,求最多能给多少位同学发礼物.

二.分析

\quad s s s 为所有礼物个数之和, m x mx mx 为所有礼物中礼物个数的最大值.

\quad m x ≥ s − m x − 1 mx \geq s - mx - 1 mxsmx1 时,可以让其他礼物排成一行,最多数量的礼物插空,保证了相邻的普通礼物不同。此时,若 m x − ( s − m x − 1 ) ≥ s − ( s − m x ) − ( s − m x − 1 ) mx - (s - mx - 1) \geq s-(s-mx) - (s-mx-1) mx(smx1)s(smx)(smx1),则答案为 2 ( s − m x − 1 ) + 1 2(s - mx - 1) + 1 2(smx1)+1;否则,需要结尾把后面的普通礼物放到神秘礼物中,答案为 s 2 \frac{s}{2} 2s.

\quad 否则,不难(用)证明答案为 s 2 \frac{s}{2} 2s.

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)1e5; const int N = (int)3e2; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = (ll)1e9 + 7; const double eps = 1e-6; int n, a[M + 5]; int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); int T; scanf("%d", &T); for(int ca = 1; ca <= T; ++ca) { scanf("%d", &n); for(int i = 1; i <= n; ++i) scanf("%d", &a[i]); int s = 0, mx = 0; for(int i = 1; i <= n; ++i) s += a[i], mx = max(mx, a[i]); int ans = 0; if(mx >= s - mx - 1) { if(2 * mx - s + 1 >= 2 * s - 2 * mx - 1) ans = 2 * s - 2 * mx - 1; else ans = s / 2; } else ans = s / 2; printf("Case #%d: %d\n", ca, ans); } return 0; }

G Mountain

一.题目大意

\quad 给一张 n × m n \times m n×m 的地图 s s s s [ i ] [ j ] ∈ { ′ ′ X ′ , ′ . ′ } s[i][j] \in \{''X', '.'\} s[i][j]{X,.}.

\quad 要求在每个位置填数,每个位置上的数 ∈ [ 1 , n × m ] \in [1, n\times m] [1,n×m] 且两两不相同,满足 ′ X ′ 'X' X 位置上的数是它周围 8 8 8 个数的最小值且 ′ . ′ '.' . 不是.

\quad 求填数的方案数.

\quad n , m ≤ 5 n,m \leq 5 n,m5.

二.分析

\quad 这数据范围显然是状压 DP.

\quad 易知图中若存在相邻的 ′ X ′ 'X' X 则方案数为 0 0 0,则图中最多存在 9 9 9 ′ X ′ 'X' X.

\quad 考虑从小到大填数, f [ i ] [ j ] f[i][j] f[i][j] 表示用了前 i i i 个数, ′ X ′ 'X' X 的填充状态为 j j j 的方案数,即得状态转移方程.

\quad 可这样只保证了 ′ X ′ 'X' X 是极小的,无法保证 ′ . ′ '.' . 不是极小的,所以这里非常妙可以用容斥来做.

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)5; const int N = (int)25; const int inf = 0x3f3f3f3f; const ll mod = (ll)772002; const double eps = 1e-6; int n, m, ans; char s[M][M]; int id[M][M]; int f[N + 1][1<<9]; int c1[1<<9], c2[1<<9]; int dx[] = {0, -1, -1, -1, 0, 1, 1, 1}; int dy[] = {1, 1, 0, -1, -1, -1, 0, 1}; bool check(int i, int j) { for(int k = 0, x, y; k < 8; ++k) { x = i + dx[k], y = j + dy[k]; if(x >= 0 && x < n && y >= 0 && y < m && s[x][y] == 'X') return 0; } return 1; } int cal(int c) { int x, y, flag; for(int i = 0; i < (1<<c); ++i) { c1[i] = 0; for(int j = 0; j < n; ++j) { for(int k = 0; k < m; ++k) { if(s[j][k] != '.') continue; flag = 1; for(int l = 0; l < 8; ++l) { x = j + dx[l], y = k + dy[l]; if(x >= 0 && x < n && y >= 0 && y < m && s[x][y] == 'X' && (((~i)>>id[x][y]) & 1)) flag = 0; } c1[i] += flag; } } } f[0][0] = 1; for(int i = 1; i <= n * m; ++i) { for(int j = 0; j < (1<<c); ++j) { f[i][j] = f[i - 1][j] * (c1[j] - (i - 1 - c2[j])) % mod; for(int k = 0; k < c; ++k) { if((j>>k) & 1) f[i][j] = (f[i][j] + f[i - 1][j^(1<<k)]) % mod; } } } return f[n * m][(1<<c) - 1]; } void dfs(int u, int v, int e) { if(u == n * m) { if(e & 1) ans = (ans - cal(v)) % mod; else ans = (ans + cal(v)) % mod; return; } int x = u / m, y = u % m; if(s[x][y] == 'X') id[x][y] = v, dfs(u + 1, v + 1, e); else { dfs(u + 1, v, e); if(check(x, y)) { s[x][y] = 'X'; id[x][y] = v, dfs(u + 1, v + 1, e + 1); s[x][y] = '.'; } } } void work() { for(int i = 0; i < n; ++i) scanf("%s", s[i]); bool f = 1; for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) if(s[i][j] == 'X' && !check(i, j)) f = 0; if(!f) {printf("0\n"); return;} ans = 0; dfs(0, 0, 0); ans = (ans % mod + mod) % mod; printf("%d\n", ans); } int getOne(int n) { int c = 0; while(n) c += (n & 1), n >>= 1; return c; } int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); for(int i = 0; i < (1<<9); ++i) c2[i] = getOne(i); int ca = 0; while(~scanf("%d %d", &n, &m)) { printf("Case #%d: ", ++ca); work(); } return 0; }

K Lweb and String

一.题目大意

\quad 给一个由小写字母组成的字符串,可将字母集合映射到任意数字集合,求将该字符串映射成数字串的严格最长上升子序列的长度.

\quad l e n g t h ( s ) ≤ 1 0 5 length(s) \leq 10^5 length(s)105.

二.分析

\quad 显然答案就是字符串的字符集大小.

三.代码实现

#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef long long ll; const int M = (int)1e5; const int N = (int)1e5; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = (ll)1e9 + 7; const double eps = 1e-6; char s[M + 5]; int cnt[26]; int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); int T; scanf("%d", &T); for(int ca = 1; ca <= T; ++ca) { memset(cnt, 0, sizeof(cnt)); scanf("%s", s + 1); int n = strlen(s + 1); for(int i = 1; i <= n; ++i) cnt[s[i] - 'a']++; int c = 0; for(int i = 0; i < 26; ++i) c += (cnt[i] > 0); printf("Case #%d: %d\n", ca, c); } return 0; }
最新回复(0)