ei1333の日記

ぺこい

Block-cut tree

う笑

二重頂点連結成分と関節点を結ぶと木になります. block-cut tree または BC-tree と呼ばれます.

f:id:ei1333:20200325010122p:plain Biconnected component - Wikipedia

二重頂点連結成分と関節点を求めるライブラリを持っているとします. 持ってなかったらごめんなさい.

それぞれの二重頂点連結成分と関節点が, Block-cut tree上での  {1} つの頂点となります.

つくりかたなんですが, それぞれの二重頂点連結成分についてそれと隣接する関節点を辺で結びます. 終わりです. (多重辺を結ばないようにしたりする処理が必要ですが, ぜんぶ線形時間でできます)

実装例はこちら!

template< typename T = int >
struct BlockCutTree : BiConnectedComponents< T > {
public:
  using BiConnectedComponents< T >::BiConnectedComponents;
  using BiConnectedComponents< T >::g;
  using BiConnectedComponents< T >::articulation;
  using BiConnectedComponents< T >::bc;

  vector< int > rev;
  vector< vector< int > > group;
  Graph< T > tree;

  explicit BlockCutTree(const Graph< T > &g) : Graph< T >(g) {}

  int operator[](const int &k) const {
    return rev[k];
  }

  void build() override {
    BiConnectedComponents< T >::build();
    rev.assign(g.size(), -1);
    int ptr = (int) bc.size();
    for(auto &idx : articulation) {
      rev[idx] = ptr++;
    }
    vector< int > last(ptr, -1);
    tree = Graph< T >(ptr);
    for(int i = 0; i < (int) bc.size(); i++) {
      for(auto &e : bc[i]) {
        for(auto &ver : {e.from, e.to}) {
          if(rev[ver] >= (int) bc.size()) {
            if(exchange(last[rev[ver]], i) != i) {
              tree.add_edge(rev[ver], i, e.cost);
            }
          } else {
            rev[ver] = i;
          }
        }
      }
    }
    group.resize(ptr);
    for(int i = 0; i < (int) g.size(); i++) {
      group[rev[i]].emplace_back(i);
    }
  }
};

ここからは実装上の話です.

Block-cut tree上の頂点番号の割り当て方なんですが, 二重頂点連結成分ならその連結成分の番号, 関節点なら (二重頂点連結成分の個数)+何番目の関節点かをしています.

rev にはもとのグラフの各頂点がblock-cut tree上のどの頂点に属するかが格納されます.

各二重頂点連結成分を見ていったときに, 隣接している関節点があってまだ結んでいなければそこと結び, 関節点ではない頂点が現れた場合にはその頂点のindexをrevに格納すればよいです.

group[i]にはblock-cut treeの頂点iに対応するもとのグラフの頂点番号たちが格納されます.

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=3022 を解きます.

それぞれの頂点とそれと結ぶ辺を取り除いたときに, 残った連結成分で重みの最大となるものを求める問題です.

ネタバレをするんですが, 関節点ではなければ取り除いても連結なので, 全体の重みの総和-その頂点の重みです. それ以外の場合はBlock-cut tree上を木DPすると解けます.

前計算としてBlock-cut Tree上の各頂点について, その頂点に含まれるもとのグラフの頂点の重みの和を計算しておきます. あとは木DPをして, 関節点ならその頂点と辺で結ぶ連結成分のうち max に更新すればよいです.

int main() {
  int N, M;
  cin >> N >> M;
  vector< int > A(N);
  cin >> A;
  BlockCutTree<> bct(N);
  bct.read(M);
  bct.build();
  vector< int64 > weight(bct.tree.size());
  int64 all = 0;
  for(int i = 0; i < N; i++) {
    weight[bct[i]] += A[i];
    all += A[i];
  }
  vector< int64 > ans(N, all);
  for(int i = 0; i < N; i++) {
    ans[i] -= A[i];
  }
  MFP([&](auto rec, int idx, int par) -> int64 {
    int64 mx = 0, sum = 0;
    for(auto &to : bct.tree.g[idx]) {
      if(to == par) continue;
      auto res = rec(to, idx);
      sum += res;
      chmax(mx, res);
    }
    if(idx >= bct.bc.size()) {
      ans[bct.group[idx][0]] = max(all - sum - weight[idx], mx);
    }
    return sum + weight[idx];
  })(0, -1);
  for(auto &p : ans) cout << p << "\n";
}