5

(これは最近完了したプログラミングコンテストから派生しています)

N個のノードとN-1個のエッジを持つ連結グラフであるGが与えられます。

(これは、Gがツリーを形成することを意味することに注意してください。)

Gの各エッジが方向付けられます。(必ずしもルートに対して上向きである必要はありません)

Gの各頂点vについて、他のすべての頂点wからvへの有向パスが存在するように0個以上のエッジを反転することができます。これを実現するためのエッジ反転の可能な最小数をf(v)とします。

どの線形または対数線形アルゴリズムによって、全体として最小のf(v)(それらの頂点のf(v)の値を含む)を持つ頂点のサブセットを決定できますか?

たとえば、次のエッジを持つ4つの頂点グラフについて考えてみます。

A<--B
C<--B
D<--B

f(A)= 2、f(B)= 3、f(C)= 2およびf(D)=2..の値

..したがって、必要な出力は{A、C、D}および2です。

(最小のf(v)を持つ頂点のf(v)のみを計算する必要があることに注意してください-すべてではありません)

コード:

後世のために、ここに解決策のコードがあります:

int main()
{
    struct Edge
    {
        bool fwd;
        int dest;
    };

    int n;
    cin >> n;

    vector<vector<Edge>> V(n+1);

    rep(i, n-1)
    {
        int src, dest;
        scanf("%d %d", &src, &dest);

        V[src].push_back(Edge{true, dest});
        V[dest].push_back(Edge{false, src});
    }

    vector<int> F(n+1, -1);

    vector<bool> done(n+1, false);

    vector<int> todo;
    todo.push_back(1);
    done[1] = true;
    F[1] = 0;

    while (!todo.empty())
    {
        int next = todo.back();
        todo.pop_back();

        for (Edge e : V[next])
        {
            if (done[e.dest])
                continue;

            if (!e.fwd)
                F[1]++;
            done[e.dest] = true;
            todo.push_back(e.dest);
        }
    }

    todo.push_back(1);

    while (!todo.empty())
    {
        int next = todo.back();
        todo.pop_back();

        for (Edge e : V[next])
        {
            if (F[e.dest] != -1)
                continue;

            if (e.fwd)
                F[e.dest] = F[next] + 1;
            else
                F[e.dest] = F[next] - 1;

            todo.push_back(e.dest);
        }
    }

    int minf = INT_MAX;

    rep(i,1,n)
        chmin(minf, F[i]);

    cout << minf << endl;

    rep(i,1,n)
        if (F[i] == minf)
            cout << i << " ";
    cout << endl;

}
4

1 に答える 1

7

次のアルゴリズムは正しく機能し、線形時間で確実に機能すると思います。

このアルゴリズムの動機は次のとおりです。ある単一ノードvのf(v)の値をすでに知っていると仮定しましょう。ここで、vに隣接するノードuについて考えます。f(u)の値を計算する場合は、からの情報の一部を再利用できます。それを計算するためにf(v)。グラフ内の任意のノードwからuに到達するには、次の2つのケースのいずれかが発生する必要があることに注意してください。

  1. そのパスは、uとvを接続するエッジを通過します。その場合、wからuに移動する方法は、wからvに移動し、次にvからuにエッジをたどることです。
  2. そのパスは、uとvを接続するエッジを通過しません。その場合、wからuに到達する方法は、uに到達するとすぐに停止することを除いて、wからvに到達する方法とまったく同じです。 。

この観察が重要である理由は、任意のノードからvに取得するためにフリップするエッジの数がわかっている場合、それを簡単に変更して、フリップして取得するエッジのセットを取得できることを意味します。 uへの任意のノード。具体的には、前と同じエッジのセットになりますが、uとvを接続するエッジを方向付けて、vをuに接続するようにします。

uからvへのエッジが最初に方向付けられている場合(u、v)、反転したすべての通常のエッジを反転して、すべてのノードがvを指すようにし、さらにもう1つのエッジを反転してvをuに戻す必要があります。したがって、f(u)= f(v)+ 1です。それ以外の場合、エッジが元々(v、u)に向けられている場合、反転するエッジのセットは以前と同じになります(すべてをvに向けます)。エッジ(v、u)を反転しないことを除いて。したがって、f(u)= f(v)-1。

したがって、単一ノードvのfの値がわかれば、隣接するノードuごとに次のように計算できます。

f(u) = f(v) + 1    if (u, v) is an edge.
f(u) = f(v) - 1    otherwise

これは、次のようにすべてのノードvのf(v)を計算できることを意味します。

  1. 任意に選択された初期ノードvのf(v)を計算します。
  2. vから開始してDFSを実行します。ノードuに到達したら、上記のロジックを使用してそのfスコアを計算します。

あとは、初期ノードのf(v)を計算するだけです。これを行うには、vから外側に向かってDFSを実行します。エッジが間違った方向を向いているのを見るたびに、それを裏返す必要があります。したがって、f(v)の初期値は、初期DFS中に検出された誤ったポインティングエッジの数によって与えられます。

したがって、初期DFSを実行して初期ノードのf(v)を計算し、次にセカンダリDFSを実行して他の各ノードuのf(u)を計算することにより、O(n)時間で各ノードのfスコアを計算できます。次に、n個のfスコアのそれぞれをforループして最小スコアを見つけ、さらに1回ループして、そのfスコアを持つすべての値を見つけることができます。これらの各ステップにはO(n)時間がかかるため、アルゴリズム全体にもO(n)時間がかかります。

お役に立てれば!これはすごい問題でした!

于 2012-08-27T20:43:27.740 に答える