トコロテンの日記

自分の活動内容や普段考えていることをアウトプットするためのブログです。ITに関わる話がメインであり、開発、競技プログラミング、気に入った技術などの話が多くなります。

AtCoder Beginner Contest 126 D - Even Relation

問題

atcoder.jp

考察と解法

世の中には2種類の頂点が存在する。白色の頂点と黒色の頂点である。

by トコロテン

はい、そうですかといった感じですね。

さて、今回の問題を整理すると以下のようになります。

  • N (1 \leq N \leq 10^{5})個の頂点を持つ重み付きの木構造のデータが入力として与えられる。
  • 同色の任意の2頂点の距離が偶数であるという条件の下、各頂点に白か黒の色を塗る。

2頂点の距離というのは、ある頂点から別の頂点へ移動する際に通る辺の重みの総和の最小値になります。

入力とそれに対応する解のイメージとしては以下の図のような感じです。ある解の各頂点の色を反転させたものもまた解となります。

f:id:tokoroten_lab:20191116144707p:plain

例を確認したところで考察をしていきます。 考察するにあたって、以下の2つの関数を定義します。  {} $$ dist(a, b) := 頂点a, b間の距離 \\ color(v) = \left\{ \begin{array}{} 0 & (頂点vが白) \\ 1 & (頂点vが黒) \end{array} \right. $$

まずはじめに、任意の頂点Sを1つ選び、頂点sに色を塗ってみます。 この際、塗る色は白と黒のどちらでもいいはずです。 なぜならば、上でも説明した通り、ある解の各頂点の色を反転させたものもまた解となるため、ある頂点が白なのか黒なのかは本質ではなく、どの頂点とどの頂点が同じ色なのかということのみが重要だからです。

解の条件  {} $$ color(a) = color(b) \Rightarrow dist(a, b) \equiv 0 \bmod 2 $$

の対偶をとると以下のようになります。  {} $$ dist(a, b) \equiv 1\bmod 2 \Rightarrow color(a) \neq color(b) $$

したがって、 \forall o (dist(s, o) \equiv 1\bmod 2)の色が 1 - color(s)に決定します。

では、反対に E = \{ e | dist(s, e) \equiv 0\bmod 2 \}とした時に \forall e \in Eについて color(e) = color(s)としてよいのかといった疑問が残ります。 これは結論からするとしてよいです。

以下を示します。  {} $$ dist(e_1, e_2) \equiv 0\bmod 2 (\forall e_1, e_2 \in E) $$

まず、sを木の根とし、 dist(e_1, s) dist(e_2, s)を達成する経路上で e_1, e_2から根の方向に向かって初めに合流する点を v_pとします。

f:id:tokoroten_lab:20191116174410p:plain

この時、  {} $$ dist(e_1, e_2) = dist(e_1, s) + dist(e_2, s) - 2 * dist(v_p, s) \\ $$

となります。また、  {} $$ dist(e_1, s) \equiv 0\bmod 2 \\ dist(e_2, s) \equiv 0\bmod 2 \\ 2 * dist(v_p, s) \equiv 0\bmod 2 $$

であるから、  {} $$ dist(e_1, s) + dist(e_2, s) - 2 * dist(v_p, s) \equiv 0 \bmod 2 $$ が成立します。

したがって、 \forall e \in E について color(e) = color(s)としてよいです。 これによって、頂点sを1つ選ぶことで残りの全ての頂点の色が頂点sとの距離の偶奇によって決定できることがわかりました。 特に、 color(s) = 0とすることで任意の頂点vについて以下が成立します。  {} $$ color(v) = dist(s, v) \bmod 2 $$

以上を踏まえて、アルゴリズムの計算量を見積もります。

計算量

適当な頂点を1つ選びそこから全ての頂点への距離を求めればよいため、計算量は O(N)となります。

実装上の注意点

木を表現するにあたって、頂点間の辺の接続情報を隣接行列で表現しようとすると各頂点について接続されている遷移先の頂点を調べるのに O(N)かかり、全体の計算量が O(N^{2})になってしまいます。 したがって、辺の接続情報は工夫して保持しましょう。具体的な実装方法は以下のコードを見てください。

ソースコード

#include <iostream>
#include <vector>
 
using namespace std;
 
void set_color(int node, int color, vector<vector<pair<int, int> > >& edges, vector<bool>& hasVisited, vector<int>& colors)
{
    colors[node] = color;
    hasVisited[node] = true;
 
    for(auto edge : edges[node])
    {
        int next_node = edge.first;
        int cost = edge.second;
        if(!hasVisited[next_node])
        {
            set_color(next_node, (color + cost) % 2, edges, hasVisited, colors);
        }
    }
}
 
int main()
{
    int n;
    vector<vector<pair<int, int> > > edges;
    vector<bool> hasVisited;
    vector<int> colors;
 
    cin >> n;
 
    edges.resize(n + 1);
    hasVisited.resize(n + 1, false);
    colors.resize(n + 1);
 
    for(int i = 0; i < n - 1; ++i)
    {
        int u, v, w;
        cin >> u >> v >> w;
        edges[u].push_back(make_pair(v, w));
        edges[v].push_back(make_pair(u, w));
    }
 
    set_color(1, 0, edges, hasVisited, colors);
 
    for(int i = 1; i <= n; ++i)
    {
        cout << colors[i] << endl;
    }
 
    return 0;
}

感想

木の気持ちになることが大切な気がします。木だけにってね🤗