競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 210E

ABC210E

MST.
解法から攻めるのは邪道な気もするが, 問題の性質から攻めて綺麗な方法が作れなかったので, MST のアルゴリズムであるクラスカル法から攻める. クラスカル法により,コストの小さい辺から追加していく. 計算量を無視すればこれで解ける.

高速化
頂点の個数 \(N\) が大きいので,そのままクラスカル法を実装できない. そこで,操作をまとめて行う. コスト \(c_{i}\) が小さい \(i\) を固定して考える. \(+ a_{i}\) の辺を用いて類別する.
\(+ a_{i}\) の辺を追加する操作を何回するのかを数える. そのためには,各類毎に操作を何回するか,類の個数はいくつか, が分かれば十分.
\(g := GCD(N, a_{i})\) とおく. 類毎に \(\frac{N}{g} - 1\) 回, 類が \(g\) 個 あるので,合計 \(g (\frac{N}{g} - 1)\) 回行う. これにコスト \(c_{i}\) を掛ける.
実装では,Union-Find を使う必要はない. 類別した後のグラフが簡単に書けるのが効いている. 新しいグラフは, 頂点数が \(g\) に変わるだけで, \(a\) は変える必要がない.

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

template<typename T>
T GCD( T a, T b ){
  const T E = 0; // unit of GCD
  if( a == E ) return b;
  if( b == E ) return a;

  if( a < b ) swap(a,b);

  T rem;
  do{
    rem = a % b;
    a = b;
    b = rem;
  }while( rem != 0 );

  return a;
}

int main() {
  ll n, m ;
  cin >> n >> m;
  vector<pll> p(m);
  rep(i,m){
    cin >> p[i].second >> p[i].first;
  }
  sort(all(p));

  ll ans = 0;
  for(auto [c,a]:p){
    ll g = GCD(n, a);
    if(g % n == 0) continue;

    ans += g*(n/g - 1)*c;

    n = g;
    if(n == 1) break;
  }
  if(n != 1) ans = -1;
  cout << ans << endl;
 

  return 0;
}