1. 程式人生 > 其它 >AtCoder Beginner Contest 220 A - F 題解

AtCoder Beginner Contest 220 A - F 題解

ABC220 從 A 題 到 F 題的題解

A - Find Multiple

for 迴圈遍歷就好了

void solve(){
  int a, b, c;
  cin >> a >> b >> c;
  for (int i = a; i <= b; ++i) {
    if (i % c == 0) {
      cout << i;
      return;
    }
  }
  cout << -1;
}

B - Base K

簡單的進位制轉換

void solve(){
  int k;
  string a, b;
  cin >> k >> a >> b;
  auto tod = [&] (string & s) {
    int res = 0;
    for (auto & ch : s) {
      res *= k;
      res += ch - '0';
    }
    return res;
  };
  cout << 1ll * tod(a) * tod(b);
}

C - Long Sequence

整個陣列可能被遍歷 k 次,k - 1 次顯然可以直接 O(1) 算出來,最後一次可以 for 迴圈遍歷

ll a[N];
 
void solve(){
  int n;
  cin >> n;
  ll sum = 0;
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
    sum += a[i];
  }
  ll x;
  cin >> x;
  ll ans = x / sum;
  x %= sum;
  ans *= n;
  ll res = 0;
  for (int i = 0; i < n && res <= x; res += a[i], ++ans, ++i);
  cout << ans;
}

D - FG operation

可以觀察發現每次就兩種操作,那麼第 i 次操作得來的數不是 F 就是 G 所以只需要考慮 F 或 G 操作具體會產生什麼數就好了

發現每次 DP 只用到上次 DP 的結果所以空間還可以優化

int a[N], f[N][10];
 
void solve(){
  int n;
  cin >> n;
  for (int i = 1; i <= n; ++i) cin >> a[i];
  f[1][a[1]] = 1;
  for (int i = 2; i <= n; ++i) {
    for (int j = 0; j < 10; ++j) {
      int p = j * a[i] % 10, q = (j + a[i]) % 10;
      f[i][p] = (f[i][p] + f[i - 1][j]) % mod;
      f[i][q] = (f[i][q] + f[i - 1][j]) % mod;
    }
  }
  for (int i = 0; i < 10; ++i) {
    cout << f[n][i] << '\n';
  }
}

E - Distance on Large Perfect Binary Tree

這個式子沒推出來,看別人題解才明白怎麼做

對於這個問題考慮枚舉出所有不同的節點 i, j保證 len(i, j) = m

對於 (i, j) 先考慮他們的 k = lca(i, j)

k 下面的深度需要保證深度至少為 depk = max(l, r), l 為 i 的深度,r 為 j 的深度

所以 k 的取法是深度為 1 到 n - depk 的上層子樹的所有節點所以有 $ 2^depk - 1 $ 種取法

當 k 固定後顯然 i 和 j 就好取了,i 是 k 的左兒子為跟節點開始直到深度為 r - 1 所在的子樹的所有葉節點,j 也同理

可能說的不是很清楚,畫下圖可能會更清晰點

ll pw2[N], ans, n, m;
 
void solve(){
  cin >> n >> m;
  pw2[0] = 1;
  for (int i = 1; i <= n; ++i) {
    pw2[i] = pw2[i - 1] * 2 % mod;
  }
  for (int l = 0; l <= m; ++l) {
    int r = m - l;
    if (l >= n || r >= n) continue;
    int depk = max(l, r);
    ll cntk = pw2[n - depk] - 1;
    (ans += (cntk * pw2[max(l - 1, 0)] % mod * pw2[max(r - 1, 0)] % mod)) %= mod;
  }
  cout << ans * 2 % mod;
}

F - Distance Sums 2

換根 dp , 這是換根 dp 專題

換根 dp 看上去雖然是個名詞但其實就是樹形 dp

之所以需要換根是因為根不同 dp 結果也會有所不同,當然不可能每個節點都作為根去 dp 遍歷整棵樹

只需要以某個節點為根進行一次 dp

然後再遍歷整棵樹,對於不同的節點只需要考慮從父節點走到當前節點會對答案造成什麼影響即可

這樣只需遍歷兩次就可以找到答案

回到這道題,第一次 dp 要做的是以 1 為根節點求出根節點到所以子節點的距離,以及每個節點的節點個數為後面的一次 dp 做準備

第二次 dp 則是以父節點的答案來求出子節點的答案

具體來說,距離顯然每次都會加 1 只需要訪問到每個節點就把這個節點的距離加到 f[1] 裡,節點個數的話sz[i] = szj + 1

然後考慮從根節點出發到每個節點距離會產生什麼影響,對當前節點 i 來說 i 這顆子樹上的所以節點距離顯然都會 -1 而除了這顆子樹上的所以節點距離都會 +1 所以 f[i] = f[u] + n - 2 * sz[i] (u 為父節點)

int head[N], ne[N], to[N], tot, n, sz[N];

ll f[N], dep[N];

void add(int u, int v) {
  to[++tot] = v;
  ne[tot] = head[u];
  head[u] = tot;
}

void solve(){
  cin >> n;
  for (int i = 1; i < n; ++i) {
    int u, v;
    cin >> u >> v;
    add(u, v), add(v, u);
  }
  function<void(int, int, int)> dfs1 = [&](int u, int par, int dep) {
    f[1] += dep;
    for (int i = head[u]; i; i = ne[i]) {
      if (to[i] != par) {
        dfs1(to[i], u, dep + 1);
        sz[u] += sz[to[i]];
      }
    }
    sz[u]++;
  };
  dfs1(1, 0, 0);
  function<void(int, int)> dfs2 = [&](int u, int par) {
    for (int i = head[u]; i; i = ne[i]) {
      if (to[i] != par) {
        f[to[i]] = f[u] + n - 2 * sz[to[i]];
        dfs2(to[i], u);
      }
    }
  };
  dfs2(1, 0);
  for (int i = 1; i <= n; ++i) cout << f[i] << '\n';
}