yutasの競技プログラミング勉強帖

競技プログラミングの問題についての解説記事を主に書いています。

F - Alex's whims / Codeforces Round 909 (Div. 3)

問題

木構造のグラフとは、 ループを含まない  n 頂点  n - 1 辺のグラフのことを指す。

 n 頂点の木構造のグラフに対して、  q 日間で整数  {d}_{i} が1日ごとに与えられる。 また、グラフは1日1回までなら、以下の操作を行うことができる。

  • ある頂点  u と、 u に隣接している頂点  {v}_{1} と隣接していない頂点  {v}_{2} を選び、 u {v}_{1} をつなぐ辺を削除して  u {v}_{2} に新たに辺をつなぐ。

 i 日目の値  {d}_{i} について、距離が  {d}_{i} である葉の組み合わせが存在しなければならないとき、それを与える  n 頂点の木構造のグラフの初期状態と、  i 日目にて選択すべき  u, {v}_{1}, {v}_{2} の値をそれぞれ求めよ。 ただし、操作を行う必要のない場合は  u = -1, {v}_{1} = -1, {v}_{2} = -1 とすること。

なお、「葉」とは「繋がっている辺が1つしかない頂点」を指す。

入力

まず最初の1行目に、テストケースの個数を表す整数  t が与えられる。

その後、 t 個のテストケースのそれぞれに対して、以下のように入力が与えられる。

  • 最初の1行目には、整数  n, q が与えられる。
  • 次の  q 行のうち  i 行目には、整数  {d}_{i} が与えられる。

条件

  •  1 \leq t \leq 100
  •  3 \leq n \leq 500
    • すべてのテストケースにおける  n の値の合計は  500 を超えない。
  •  1 \leq q \leq 500
    • すべてのテストケースにおける  q の値の合計は  500 を超えない。
  •  2 \leq {d}_{i} \leq n - 1

出力

それぞれのテストケースについて、以下のように出力すること。

  • まず最初の  n - 1 行には、初期状態の木構造グラフの辺の状態を出力すること。
    • すなわち、ある辺が頂点  u, v を繋いでいるときには、  u, v の値を1行で出力し、それを  n - 1 本分行うこと。
  • 次の  q 行には、  i 日目における  u, {v}_{1}, {v}_{2} の値をそれぞれ1行ずつ出力すること。

解法

 n 頂点の木構造のグラフとして、一番単純な以下の図のようなものをまずは考えます。 なお、図の赤い頂点は葉を表しています。

このグラフは葉同士の距離が  n - 1 であり、  {d}_{i} = n - 1 の条件を満たすグラフもこの形のもののみとなります。 そのため、このグラフをもとに辺の位置を変えて、すべての  {d}_{i} に対応できるような方法を考えます。

与えられる  {d}_{i} は葉同士の距離を与えるものなので、「ある葉に対して、変更後も葉になるように接続されている辺の変更をする」ことを行うと、条件を満たしやすくなると考えることができます。

例えば、先ほどのグラフに対して、頂点  n に接続されている辺の接続先を頂点  n - 1 から頂点  i (ただし、  2 \leq i \leq n - 1)へと変更することを考えます。 このとき、変更後のグラフは以下のようになります。

図から示されるように、ここで頂点  1, n は変更後も葉のままです。 また、頂点  1 から頂点  i までの距離は  i - 1 であるため、頂点  1 から頂点  n までの距離は  i となります。

以上の考察から、  2 \leq {d}_{i} \leq n - 1 であるので、 {d}_{i} が与えられたら、頂点  n の接続先の頂点を  {d}_{i} に変更することで、条件を満たせると言えます。

従ってこの問題を解くためには、まず「頂点  1 と頂点  2 、頂点  2 と頂点  3 、……、頂点  n - 1 と頂点  n」がそれぞれ接続されている木構造のグラフを初期状態として与えます。

そこからこのグラフの頂点  n を必ず  u = n として、与えられた  {d}_{i} を用いて  {v}_{1} = (直前に頂点  n が接続されていた頂点)とし、  {v}_{2} = {d}_{i} とすることで、答えが得られます。 ただし、  {v}_{1} = {v}_{2} となる場合については辺の変更を行う必要がないので、  u = -1, {v}_{1} = -1, {v}_{2} = -1 とします。

最初のグラフを出力する部分については  O(n) ででき、その後の  q 個のクエリについても  O(q) で処理できるため、この問題は  O(n + q) の計算量で解くことができ、これは実行時間制限にも十分間に合います。

ソースコード

グラフ及び  u, {v}_{1}, {v}_{2} を出力するための関数 solve() を以下のように実装しました。

int n, q, d[505];

void solve() {
  // 値の入力
  cin >> n >> q;
  for (int i = 1; i <= q; i++) {
    cin >> d[i];
  }

  // 初期状態のグラフの出力
  for (int i = 1; i <= n - 1; i++) {
    cout << i << " " << i + 1 << endl;
  }

  int connected = n - 1; // connected は頂点 n に接続されている頂点を表す

  for (int i = 1; i <= q; i++) {
    if (connected == d[i]) { // 新しい接続先が connected と一致していれば、 u = v_1 = v_2 = -1 となる
      cout << "-1 -1 -1" << endl;
    } else {
      cout << n << " " << connected << " " << d[i] << endl;
      connected = d[i]; // connected の更新
    }
  }
  return;
}

感想

問題文自体が長いので、行うべきことを理解するための時間はかかりましたが、それが分かれば D問題E問題 よりも簡単なように思いました。