AtCoder Beginner Contest 399
儒烏風亭いおり

E - Replace

转换为图论问题,S[i] 指向 T[i] 建边。相当于 26 个点,每一个点的出度最多为 1,否则无解。

答案基本上等于边的数量 - 自环的数量。只是存在环的联通块需要特殊考虑。

  1. 这个环上的每一个点入度出度都等于 1,出现死循环,需要拆环。而拆环需要找到一个入度为 0 的点,也就是判断是否存在一个中间点。如果存在,那么答案+1,否则无解。
  2. 这个环上,存在某个点 X 的入度大于 1,又已知每个点的出度最大为 1,那么假设有环上某点 Y,环外某点 Z 指向 X。 于是可以将环拆开,将 Y -> X 转变为 Y -> Z。先做一次 Y-> Z,那么环就拆成了链条。这不产生额外开销。

图解:

ABC_399_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
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>

bool vis[26][26];
int row[26], col[26], nxt[26];
int res = 0;

int main() {
  int N;
  std::cin >> N;

  std::string S, T;
  std::cin >> S >> T;

  for (int i = 0; i < N; i++) {
    vis[S[i] - 'a'][T[i] - 'a'] = true;
  }

  for (int i = 0; i < 26; i++) {
    for (int j = 0; j < 26; j++) {
      row[i] += vis[i][j];
      col[j] += vis[i][j];
      if (vis[i][j])
        nxt[i] = j;
    }
    if (row[i] >= 2) {
      std::cout << -1 << std::endl;
      return 0;
    }
  }

  bool flag = false;
  for (int i = 0; i < 26; i++) { // 遍历不需要额外开销的连通块
    if (col[i] == 0)
      flag = true;
    if (row[i] == 0 || col[i] != 0)
      continue;

    int u = i;
    while (row[u] != 0) {
      res++;
      row[u] = 0;
      u = nxt[u];
      if (u == nxt[u]) // 这里要判断末尾的自环
        break;
    }
  }

  int cnt = 0;
  for (int i = 0; i < 26; i++) { // 剩下的都是“纯”环
    if (row[i] == 0 || nxt[i] == i)
      continue;

    int u = i;
    while (row[u] != 0) {
      cnt++;
      row[u] = 0;
      u = nxt[u];
    }
    cnt++;
  }

  if (cnt != 0 && flag == false) {
    std::cout << -1 << std::endl;
    return 0;
  }

  res += cnt;

  std::cout << res << std::endl;
  return 0;
}

赛中想到了转化为图论,想到了拆环。花了很长时间想到了特殊情况 2。

赛后发现,第一次遍历连通块时没有考虑到自环。

F - Range Power Sum

计数题,把表达式转化一下就好了。

图解:

ABC_399_F

在推式子的时候,二项式展开忘记了组合数,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
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>
using i64 = long long;

const i64 mod = 998244353;

int main() {
  int N, K;
  std::cin >> N >> K;

  std::vector<i64> A(N + 1);
  for (int i = 1; i <= N; i++)
    std::cin >> A[i];

  /**
    S[i][k] = (A[1] + A[2] + ... + A[i]) 的 k 次方
    S[0][k] = 0
    S[i][0] = 1
    s[0][0] = 1
   */
  std::vector<std::vector<i64>> S(N + 1, std::vector<i64>(K + 1));
  for (int i = 0; i <= N; i++) {
    S[i][0] = 1;
    S[i][1] = i ? (S[i - 1][1] + A[i]) % mod : 0;
    for (int k = 2; k <= K; k++)
      S[i][k] = S[i][k - 1] * S[i][1] % mod;
  }

  /**
    F[i][k] = (S[0][k] + S[1][k] + ... + S[i][k])
   */
  std::vector<std::vector<i64>> F(N + 1, std::vector<i64>(K + 1));
  for (int k = 0; k <= K; k++) {
    F[0][k] = S[0][k];
    for (int i = 1; i <= N; i++) {
      F[i][k] = (F[i - 1][k] + S[i][k]) % mod;
    }
  }

  std::vector<std::vector<i64>> C(K + 1, std::vector<i64>(K + 1));
  for (int i = 1; i <= K; i++) {
    C[i][0] = C[i][i] = 1;
    for (int j = 1; j < i; j++) {
      C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }
  }

  i64 res = 0;
  for (int i = 1; i <= N; i++) {
    int flag = 1;
    for (int t = K; t >= 0; t--) {
      i64 val = S[i][t] * flag * F[i - 1][K - t] % mod * C[K][t] % mod;
      val = (val + mod) % mod;
      res = (res + val) % mod;
      flag *= -1;
    }
  }

  std::cout << res << std::endl;

  return 0;
}

之后去看了下题解,思路是 dp,转换为隔板分小球的模型,理解难度不是很大,想法很巧妙。时间复杂度没我的解法优秀。

比赛链接:https://atcoder.jp/contests/abc399
题解:https://atcoder.jp/contests/abc399/editorial