競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 331E問題

ABC331E

解法0:

Priority queue とソートを用いて,調べる候補を減らす. \(a\)と\(b\)を降順ソートしておけば,基本的には \(a\)と\(b\)の先頭から選んでいけばよい. 今調べている\(a\)と\(b\)のインデックスの組を \(\,(i,j)\,\) とおくと, 次に調べるべきは \(\,(i+1,j)\,\) または \(\,(i,j+1)\,\) となる. これらのうち \(a\)と\(b\)の和が大きいほうが次の候補となるので, どちらを選べば良いのか判定するために priority_queue に入れてから取り出せばよい. あとは,ダメな組み合わせがあるので,それを判定すれば AC.

解法1: \(L^{1/2}\)

\(a\), \(b\) を降順ソートしておく. \(a\), \(b\) の候補をそれぞれ \(L\)個ずつ先頭から試してしまうと \(L^2\)個で TLE. そこで,\(a\), \(b\)の候補を,一方だけでも \(L^{1/2}\)程度にしてみる.
実際,\(a_{i}+b_{j}\)の組み合わせとして調べるべき組 \(\,(i,j)\,\) は,

  • (\(i \in \lceil L^{1/2} \rceil\) and \(j \in M\)) or
  • (\(i \in N\) and \(j \in \lceil L^{1/2} \rceil\) )

で十分.この条件を \(P\)とおく.
\(P\)の否定を考えれば, \(i \geq \lceil L^{1/2} \rceil\) and \(j \geq \lceil L^{1/2} \rceil\) となるので, \(P\) の中に上位 \(L\)個以上が含まれている.

実装の注意

ソートにより index がずれるので,駄目な組み合わせ \(L\) 個に含まれる判定に注意. 例えば,ソート前の配列を残しておけば対処できる. ソート前を残さない場合は,ソートに対応する置換を覚えておくことになる.

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

// priority queue
int main() {
  ll n,m,l;
  cin >> n >> m >> l;
  vector<pll> pa(n); rep(i,n) {cin >> pa[i].first; pa[i].second = i;}
  vector<pll> pb(m); rep(i,m) {cin >> pb[i].first; pb[i].second = i;}
 
  sort(all(pa), greater<pll>());
  sort(all(pb), greater<pll>());
  vector<ll> perma(n); rep(i,n) perma[pa[i].second] = i;
  vector<ll> permb(m); rep(i,m) permb[pb[i].second] = i;
 
  set<pll> bad;
  rep(i,l){
    ll c,d; cin >> c >> d;
    --c, --d;
    bad.insert({perma[c], permb[d]});
  }

 
  set<pll> use;
  priority_queue<tuple<ll,ll,ll>> pq;
  pq.push({pa[0].first + pb[0].first, 0, 0});
  rep(_,l+1){
    auto [val, i,j] = pq.top(); pq.pop();
   
    if(!in(pll(i,j), bad)){
      cout << val << endl;
      exit(0);
    }

    auto f = [&](ll i, ll j) -> void {
      if(i >= pa.size() || j >= pb.size()) return;
      if(!in(pll(i,j), use)){
        use.insert({i,j});
        pq.push({pa[i].first + pb[j].first, i, j});
      }
    };
   
    f(i+1, j);
    f(i, j+1);

   
  }
  assert(false);

  return 0;
}

// L^{1/2}
int main() {
  ll n,m,l;
  cin >> n >> m >> l;
  ll sql = sqrt(l) + 5;

  // ソート前の配列も残しておく
  vll a(n); rep(i,n) { cin >> a[i]; }
  vll b(m); rep(i,m) { cin >> b[i]; }

  // index は,ソート前の配列の物
  vector<pll> pa(n); rep(i,n) pa[i] = {a[i], i};
  vector<pll> pb(m); rep(i,m) pb[i] = {b[i], i};
  sort(all(pa), greater<pll>());
  sort(all(pb), greater<pll>());

  vector<set<ll>> banned(n);
  rep(i,l){
    ll c,d; cin >> c >> d;
    --c, --d;
    banned[c].insert(d);
  }

  ll ans = -1;
  rep(i,n) rep(j,min(sql,m)){
    auto [va,ia] = pa[i];
    auto [vb,ib] = pb[j];
    if(!in(ib, banned[ia]))
      chmax(ans, va + vb);
  }

  rep(j,m) rep(i,min(sql,n)){
    auto [va,ia] = pa[i];
    auto [vb,ib] = pb[j];
    if(!in(ib, banned[ia]))
      chmax(ans, va + vb);
  }

  assert(ans >= 0);
  cout << ans << endl;
 
  return 0;
}