競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 267E

ABC267E

最大値の最小化は, binary search が典型. しかし,解法から攻めるのは王道とは言えない. まずは性質を考える.

単調性 \(\cdots (*)\)
取り除く作業を繰り返してく. ある時点でコスト \(y\) 以下の vertex は,それ以降もずっと コストが \(y\) 以下,という単調性がある. この単調性に基づいて binary search を行う.

貪欲\(\cdots (**)\)
先ほどの単調性から,除いてよい vertex は先に取り除いても損をしない. このことから,コストの小さい順に貪欲に削除出来そうである.

解法0: Binary search, 単調性
\(x \in Codom(Cost)\) を固定する. (コスト \(x\) 以下となる取り除き方が存在する) \(\in Bool\) は単調性がある.
true になることは, ある取り除き方が存在して, 取り除くすべてのタイミング \(i \in N\) に対して (\(i\) のコスト ) \(\leq x\) となることと同値.
また,単調性 \((*)\) より,(\(i\) のコスト) \(\leq x\) となる 頂点 \(i\) は,すぐに取り除いてよいので, queue に入れておく.
空間計算量は \( O (N+M) \), 時間計算量は \( O ( (N+M) log\, K) \). ここで,\(K\) は最大ケースのコスト.

解法1: Priority queue, 貪欲
貪欲 \((**)\) より,コストが小さい頂点から貪欲に消していく 最前手が存在する.
実装コードでは, 余分に priority queue に pair が入っているが, コストが低いほうが優先される. よって,コストの大きいほうは使われないでアルゴリズムが完了する.
空間計算量は \( O (N+M) \), 時間計算量は \( O ( (N+M) log\, (N+M) )\).

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

// binary search
int main() {
  ll n, m ;
  cin >> n >> m;
  vll v(n); rep(i,n) { cin >> v[i]; }

  vector<vector<pll>> ed(n);
  rep(i,m){
    ll a,b; cin >> a >> b;
    --a,--b;
    ed[a].emplace_back(b,i); // index of edge は不要
    ed[b].emplace_back(a,i);
  }
 
  ll ok, ng;
  ok = 1e18, ng = -1;
  while(abs(ok-ng) > 1){
    ll md = (ok + ng)/2;

    vll cost(n);
    rep(a,n){
      for(auto [b,i]: ed[a]){
        cost[a] += v[b];
      }
    }

    queue<ll> q;
    rep(i,n){
      if(cost[i] <= md) q.push(i);
    }

    // vector<bool> r_e(m); // removed edge
    vector<bool> r_v(n); // removed vertex
    while(q.size()){
      ll cu = q.front(); q.pop();
      if(r_v[cu]) continue;
      r_v[cu] = true;

      for(auto [ne, i]: ed[cu]) if(!r_v[ne]) { // if(!r_e[i])
        cost[ne] -= v[cu];
        // r_e[i] = true;
        if(cost[ne] <= md) q.push(ne);
      }

    }
    bool f = true;
    rep(i,n) if(!r_v[i]) f = false;

    if(f) ok = md;
    else ng = md;
  }
  cout << ok << endl;
 
  return 0;
}


// min priority queue
int main() {
  ll n, m ;
  cin >> n >> m;
  vll v(n); rep(i,n) { cin >> v[i]; }

  vector<vector<ll>> ed(n);
  vll cost(n);
  rep(i,m){
    ll a,b; cin >> a >> b;
    --a,--b;
    ed[a].emplace_back(b);
    ed[b].emplace_back(a);
    cost[a] += v[b];
    cost[b] += v[a];
  }
 
  min_priority_queue<pll> q;
  rep(i,n) q.push({cost[i],i});
 
  vector<bool> r_v(n);
  ll ans = -INFL;
  while(q.size()){
    auto [co, vi] = q.top(); q.pop();
    if(r_v[vi]) continue;
    r_v[vi] = true;

    ll s = 0;
    for(auto nvi: ed[vi]) if(!r_v[nvi]){
      s += v[nvi];
      cost[nvi] -= v[vi];
      q.push({cost[nvi], nvi});
    }
    chmax(ans, s);
  }

  rep(i,n) if(!r_v[i]) assert(false);
  cout << ans << endl;


  return 0;
}