公式

G - ボールの転送ゲーム / Ball Passing Game 解説 by physics0523


ある子供 \(a\) から始めてボールの流れを追うことを考えましょう。
例えば、 \(a \rightarrow b \rightarrow \dots \rightarrow x \rightarrow y \rightarrow \dots \rightarrow x\) のように、有向グラフで描いた時にどこかでループができることが分かります。
更に観察すると、以下のことが分かります。

  • ループに属しているなら、そのループを全員通って自分に戻ってくる。
  • ループに属していないなら、そこから向かうことのできる唯一のループに加えてそのループに辿り着くまでの全員を通る。

このことから、以下の解法が成立します。

  • 最初にループを全て検出する。これは、入次数が \(0\) である頂点を次々と取り除くことで実現可能です。
  • 次に、ループ内の頂点の解を、そのループの頂点数とする。
  • 最後に、ループ外の頂点の解を求める。
    • 基本的には、ループにぶつかるまで \(i\) から \(T_i\) に進むことを繰り返せばよい。
    • 解を求める際に通った頂点は全て解を確定させることができるので、メモ化再帰するという方法がある。

実装例 (C++):

#include<bits/stdc++.h>
#include<atcoder/dsu>

using namespace std;
using namespace atcoder;

vector<int> mem;
vector<int> T;

int f(int v){
  if(mem[v]!=-1){return mem[v];}
  mem[v]=f(T[v])+1;
  return mem[v];
}

int main(){
  int N,Q;
  cin >> N >> Q;
  T.resize(N+1);
  vector<int> deg(N+1,0);
  for(int i=1;i<=N;i++){
    cin >> T[i];
    deg[T[i]]++;
  }
  queue<int> q;
  for(int i=1;i<=N;i++){
    if(deg[i]==0){q.push(i);}
  }
  while(!q.empty()){
    auto od=q.front(); q.pop();
    deg[T[od]]--;
    if(deg[T[od]]==0){
      q.push(T[od]);
    }
  }
  dsu d(N+1);
  for(int i=1;i<=N;i++){
    if(deg[i]){
      d.merge(i,T[i]);
    }
  }
  mem.resize(N+1);
  for(auto &nx : mem){nx=-1;}
  for(int i=1;i<=N;i++){
    if(deg[i]){mem[i]=d.size(i);}
  }
  while(Q--){
    int S;
    cin >> S;
    cout << f(S) << "\n";
  }
  return 0;
}

投稿日時:
最終更新: