Official

E - Blackout 2 Editorial by physics0523


「電線を切る」操作はグラフ理論の用語に言い換えると「辺を削除する」操作ですが、グラフにとって「辺を削除する」操作は基本的には「辺を追加する」操作に比べて大変であることが多いです。
なので、どうにかして「辺を削除する」操作を「辺を追加する」操作に言い換えましょう。

今回は、操作を「逆から見る」ことが有効です。
端的に述べると、「電線を切る」のではなく「電線を繋ぐ」ことを考えます。
問題を以下のように言い換えましょう。

最初、 \(N\) 個の都市と \(M\) 個の発電所があり、いくつかの地点は電線で繋がっています。
(元の問題で最後まで切れなかった電線だけを、最初から繋がっているものとして取り扱います。)

その後、 \(Q\) 個のイベントが与えられます。(このイベントは、元の問題と逆の順番に並べておきます。)
各イベントでは以下のことが起こります。

  • まず、現時点で電気が通っている都市の数を確認する。
  • その後、地点 \(U_i\) と地点 \(V_i\) との間を電線で接続する。

この問題が解ければ、もとの問題も解けることになります。
辺を追加していき連結成分の中に発電所があるかどうかという問題を解くことは、 Union-Find の得意分野です。
以下の流れで辺を追加しながら現時点で電気が通っている都市の数を求めることができます。

  • 既に地点 \(U_i\)\(V_i\) が同じ連結成分に存在している場合、その辺の追加を無視する。
  • そうでない場合、まず地点 \(U_i,V_i\) の存在する連結成分に発電所があるかを確認する。
    • 両方の連結成分に発電所がある / 両方の地点に発電所がない 場合は新たに電気が通る都市はない。
    • 片方の成分に発電所がありもう片方にはない場合、発電所がない側に含まれる都市が新たに電気が通る都市となります。(この数は何らかの手段で管理しておく必要があります。ただし、発電所がない側に含まれる地点は全て電気が通っていない都市であるため、この数は発電所がない側の連結成分のサイズと一致します。)
  • その後、 \(2\) つの連結成分を併合する。

なお、頂点に付いた重みの連結成分についての和を管理する機能があるような Union-Find を持っていない場合でも、通常の Union-Find の根(代表元)を返す機能を活用する (例えば ACL 内の dsu では .leader() という機能があります。実装例を参照してください。) ことで同等の機能を実現することができます。

実装例(C++):

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

using namespace std;
using namespace atcoder;

int main(){
  int n,m,e;
  cin >> n >> m >> e;
  vector<int> u(e),v(e);
  for(int i=0;i<e;i++){
    cin >> u[i] >> v[i];
    u[i]--;v[i]--;
  }

  vector<int> fl(e,1);
  int q;
  cin >> q;
  vector<int> query(q);
  for(auto &nx : query){
    cin >> nx;
    nx--;
    fl[nx]=0;
  }

  dsu uf(n+m);
  vector<int> w(n+m,0);
  for(int i=n;i<n+m;i++){w[i]=1;}
  int cur=0;
  for(int i=0;i<e;i++){
    if(fl[i]==0){continue;}
    if(uf.same(u[i],v[i])){continue;}
    int fu=w[uf.leader(u[i])];
    int fv=w[uf.leader(v[i])];
    if(fu==0 && fv==1){cur+=uf.size(u[i]);}
    if(fu==1 && fv==0){cur+=uf.size(v[i]);}
    uf.merge(u[i],v[i]);
    w[uf.leader(u[i])]=max(fu,fv);
  }
  
  vector<int> res;
  for(int j=q-1;j>=0;j--){
    int i=query[j];
    res.push_back(cur);
    if(uf.same(u[i],v[i])){continue;}
    int fu=w[uf.leader(u[i])];
    int fv=w[uf.leader(v[i])];
    if(fu==0 && fv==1){cur+=uf.size(u[i]);}
    if(fu==1 && fv==0){cur+=uf.size(v[i]);}
    uf.merge(u[i],v[i]);
    w[uf.leader(u[i])]=max(fu,fv);
  }
  
  reverse(res.begin(),res.end());
  for(auto &nx : res){cout << nx << "\n";}
  return 0;
}

posted:
last update: