公式

D - Integer-duplicated Path 解説 by physics0523


頂点 \(1\) から頂点 \(k\) までのパスを毎回求めなおしているようでは時間計算量が \(O(N^2)\) となり実行時間制限に間に合いません。
全てのパスに対する答えを何らかの方法でまとめて高速に挙げる必要があります。どのようにすればよいでしょうか?

以下のような 深さ優先探索(DFS) を考えます。

  • 頂点 \(1\) への侵入から始める。
  • 頂点 \(v\) へ侵入するタイミングで、頂点 \(v\) に関する情報を追加する。
  • 頂点 \(v\) から退出するタイミングで、頂点 \(v\) に関する情報を削除する。

このようにすることで、頂点 \(v\) に侵入し頂点 \(v\) に関する情報を追加した時点で、頂点 \(1\) から頂点 \(v\) へのパスを構成する頂点集合に関する情報が得られています。

これを用いて、この問題の答えが求められるようにします。

  • 最初は空である集合 \(S\) 、最初 \(0\) である変数 \(cnt\) を持っておく。
  • 頂点 \(v\) への侵入
    • \(S\) 内に既に \(A_v\) があれば、 \(cnt\)\(1\) 加算する。
      • (頂点 \(1\) を根と解釈した場合に) \(v\) の祖先に既に \(A_v\) が含まれるので、そのことを記憶します。
    • そうでないなら \(S\)\(A_v\) を追加する。
      • \(v\) の祖先に \(A_v\) が含まれないので、子孫を考える時に \(A_v\) の存在を検知できるようにします。
    • その後、 \(cnt>0\) なら Yes\(cnt=0\) なら No と判断する。
  • 頂点 \(v\) からの退出
    • もし進入時 \(S\)\(A_v\) を追加していた場合、 \(A_v\) を削除する。
      • このケースでは \(v\) の祖先に \(A_v\) は存在しません。
    • そうでないなら、 \(cnt\) から \(1\) 減算する。
      • このケースではまだ \(v\) の祖先に \(A_v\) は存在しますが、頂点 \(v\) での重複の検知を解除する必要があります。

例えば C++ の set を利用して実装すると、時間計算量が \(O(N \log N)\) となります。

この問題に関する発展的話題として、 Euler Tour があります。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

int n;
vector<int> a;
vector<vector<int>> g;

int cnt=0;
set<int> s;
vector<int> ans;

void dfs(int v,int pv){
  int del=0;
  if(s.find(a[v])!=s.end()){del=1;}
  else{s.insert(a[v]);}
  cnt+=del;
  if(cnt>0){ans[v]=1;}
  else{ans[v]=0;}
  for(auto &nx : g[v]){
    if(nx==pv){continue;}
    dfs(nx,v);
  }
  cnt-=del;
  if(del==0){
    s.erase(a[v]);
  }
}

int main(){
  cin >> n;
  a.resize(n);
  for(auto &nx : a){cin >> nx;}
  g.resize(n);
  for(int i=1;i<n;i++){
    int u,v;
    cin >> u >> v;
    u--; v--;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  ans.resize(n);
  dfs(0,-1);
  for(auto &nx : ans){
    if(nx){cout << "Yes\n";}
    else{cout << "No\n";}
  }
  return 0;
}

投稿日時:
最終更新: