AtCoder arc166 B - Make Multiples

B - Make Multiples

公式解説 を元に解いたのでメモ。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;
template<class T> void chmin(T& a, T b) { a = min(a,b); }

int main() {
  ll n,a,b,c; cin >> n >> a >> b >> c;
  vector<ll> x(n);
  REP(i,n) cin >> x[i];
  ll ab = lcm(a,b);
  ll ac = lcm(a,c);
  ll bc = lcm(b,c);
  ll abc = lcm(ab,c);
  vector<ll> r = { 1, a, b, ab, c, ac, bc, abc };

  auto f = [](ll x, ll y) -> ll { return (y - (x%y)) % y; };

  vector dp(n+1,vector<ll>(1<<3,2e18));
  dp[0][0] = 0;
  REP(i,n) REP(bit,1<<3) REP(j,(int)r.size()) {
    chmin(dp[i+1][bit | j], dp[i][bit] + f(x[i],r[j]));
  }

  cout << dp[n][(1<<3) - 1] << endl;
  return 0;
}

AtCoder arc166 A - Replace C or Swap AB

A - Replace C or Swap AB

公式解説 を元に解いたのでメモ。

解説を見てもなかなかシュッと実装できなくて難しい

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  int t; cin >> t;
  REP(_,t) {
    int n; string x,y;
    cin >> n >> x >> y;
    x += 'C'; y += 'C'; // 番兵
    n++; // 番兵分を +1
    map<char,vector<int>> mx,my;

    auto check = [&]() -> bool {
      int diff = my['A'].size() - mx['A'].size();
      // X の A の数が多ければ false
      // Y の A の数が多いときは X の C のよりも差分多ければ false
      if (diff < 0 || (int)mx['C'].size() < diff) return false;

      // X の C を早く出現するものから A に置き換える
      REP(i,diff) mx['A'].push_back(mx['C'][i]);
      sort(mx['A'].begin(),mx['A'].end());
      // 両者の A の出現位置を調べる
      REP(i,(int)my['A'].size()) if (my['A'][i] < mx['A'][i]) return false;
      return true;
    };

    bool ok = true;
    REP(i,n) {
      mx[x[i]].push_back(i);
      my[y[i]].push_back(i);
      if (y[i] == 'C') {
        ok = ok && x[i] == 'C' && check();
        mx.clear(); my.clear();
      }
    }
    cout << (ok ? "Yes" : "No") << endl;
  }
  return 0;
}

AtCoder abc323 参加メモ

UNIQUE VISION Programming Contest 2023 Autumn(AtCoder Beginner Contest 323) - AtCoder

B - Round-Robin Tournament

それぞれ勝った回数をカウントして、回数の多い順に sort する。

勝ち数が同じ場合には番号の早い順となるため注意。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using P = pair<int,int>;

int main() {
  int n; cin >> n;
  vector<int> cnt(n);
  REP(i,n) {
    string s; cin >> s;
    REP(j,n) if (s[j] == 'o') cnt[i]++;
  }

  vector<P> p(n);
  REP(i,n) p[i] = { cnt[i], -(i+1) }; // 勝ち数が同じ場合は番号の早い順
  sort(p.rbegin(),p.rend());

  for(auto [_,v]: p) cout << -v << " ";
  return 0;
}

C - World Tour Finals

他のプレイヤーの総合得点を上回るまで、貪欲に得点の高い問題から解いていけば良い。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using P = pair<int,int>;

int main() {
  int n,m; cin >> n >> m;
  vector<int> a(m);
  vector<string> s(n);
  REP(i,m) cin >> a[i];
  REP(i,n) cin >> s[i];

  vector<int> score(n);
  REP(i,n) {
    REP(j,m) if (s[i][j] == 'o') score[i] += a[j];
    score[i] += (i+1);
  }

  vector<P> p(m);
  REP(i,m) p[i] = {a[i],i};
  sort(p.rbegin(),p.rend());
  vector<int> order(m);
  REP(i,m) order[i] = p[i].second;

  int mx = *max_element(score.begin(),score.end());
  REP(i,n) {
    int ans = 0;
    for(auto j: order) {
      if (score[i] >= mx) break;
      if (s[i][j] == 'o') continue;
      score[i] += a[j];
      ans++;
    }
    cout << ans << endl;
  }
  return 0;
}

D - Merge Slimes

貪欲にサイズの小さいスライムから合成していく

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;

int main() {
  int n; cin >> n;
  map<ll,ll> mp;
  REP(i,n) {
    int s,c; cin >> s >> c;
    mp[s] += c;
  }

  int ans = 0;
  for(auto [size,cnt]: mp) {
    mp[size*2] += cnt/2;
    ans += cnt % 2;
  }
  cout << ans << endl;
  return 0;
}

E - Playlist

DP で解いた。

  • dp[i][j] = 時刻 i のときに j:0(1番目), j:1(1番目以外) の曲が流れているときの確率
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using mint = modint998244353;

int main() {
  int n,x; cin >> n >> x;
  vector<int> t(n);
  REP(i,n) cin >> t[i];

  // dp[i][j] := 時刻 i のときに j:0(1番目), j:1(1番目以外) になる確率
  vector dp(x+2,vector<mint>(2));
  mint r = (mint)1/n;
  REP(i,n) dp[min(x+1,t[i])][i == 0 ? 0 : 1] += r;

  REP(i,x+1) REP(j,n) REP(k,2) {
    if (dp[i][k] == 0) continue;
    dp[min(x+1,i + t[j])][j == 0 ? 0 : 1] += dp[i][k]*r;
  }

  cout << dp[x+1][0].val() << endl;
  return 0;
}

雑感

C で凡ミスして2WA、E は方針はすぐたったが、確率計算がうまくあわなくてかなりロスした。 確率や期待値問題、どうしても苦手意識がある

tic40さんのユニークビジョンプログラミングコンテスト2023 秋 (AtCoder Beginner Contest 323)での成績:1824位
パフォーマンス:1266相当
レーティング:1133→1147 (+14) :)
#AtCoder #ユニークビジョンプログラミングコンテスト2023秋(ABC323) https://atcoder.jp/users/tic40/history/share/abc323?lang=ja 

AtCoder abc322 参加メモ

AtCoder Beginner Contest 322 - AtCoder

B - Prefix and Suffix

問題文通りに愚直にやる

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  int n,m; cin >> n >> m;
  string s,t; cin >> s >> t;

  auto t1 = t.substr(0,n);
  auto t2 = t.substr(m-n);
  int ans = 3;
  if (s == t1 && s == t2) ans = 0;
  if (s == t1 && s != t2) ans = 1;
  if (s == t2 && s != t1) ans = 2;
  cout << ans << endl;
  return 0;
}

C - Festival

二分探索でやると楽

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  int n,m; cin >> n >> m;
  vector<int> a(m);
  REP(i,m) cin >> a[i];
  for(int i = 1; i <= n; i++) {
    auto it = lower_bound(a.begin(),a.end(),i);
    cout << *it - i << endl;
  }
  return 0;
}

D - Polyomino

ポリオミノはそれぞれ 回転(4) x 置く位置(4x4) = 64 通り置くパターンがある(重複やグリッドからはみ出るケースも含む)。それが3つあるため全探索すると 643 通り。これなら全探索でいける。

実装が重くプログラミング力が試される問題だった。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using P = pair<int,int>;
const int INF = 1e9;

int main() {
  vector p(3,vector<string>(4));
  REP(i,3) REP(j,4) cin >> p[i][j];

  auto rorate90 = [&](vector<string> &s) -> void {
    auto t = s;
    REP(i,4) REP(j,4) t[i][j] = s[3-j][i];
    s = t;
  };

  auto reset = [&](vector<P> &v) -> void {
    int minx = INF, miny = INF;
    for(auto [x,y]: v) {
      minx = min(minx,x);
      miny = min(miny,y);
    }
    for(auto& [x,y]: v) { x -= minx; y -= miny; }
  };

  // pa[i][j] := i番目のポリオミノを (j+1)*90 度回転させたときの座標の集合
  vector pa(3,vector(4,vector<P>()));
  REP(k,3) REP(r,4) {
    rorate90(p[k]);
    REP(i,4) REP(j,4) if (p[k][i][j] == '#') pa[k][r].emplace_back(i,j);
    reset(pa[k][r]); // 左上に詰める
  }

  auto dfs = [&](auto self, int i, vector<vector<bool>> now) {
    if (i == 3) {
      REP(ni,4) REP(nj,4) if (!now[ni][nj]) return false;
      return true;
    }

    bool res = false;
    for(auto npa: pa[i]) {
      REP(ni,4) REP(nj,4) {
        auto nnow = now;
        bool ok = true;
        for(auto [x,y]: npa) {
          auto check = [&]() -> bool {
            int nx = x+ni, ny = y+nj;
            if (nx < 0 || ny < 0 || nx >= 4 || ny >= 4) return false;
            if (nnow[nx][ny]) return false;
            return nnow[nx][ny] = true;
          };
          ok = ok && check();
        }
        res = res || (ok && self(self,i+1,nnow));
      }
    }
    return res;
  };

  bool ans = dfs(dfs,0,vector(4,vector<bool>(4)));
  cout << (ans ? "Yes" : "No") << endl;
  return 0;
}

E - Product Development

DPで解いた。 p <= 5 と小さいため p+1 進数で各パラメータの状態を持つようにした(p <= 5, k <= 5 なので最大でも 65 の状態)

dp[i][j] := i まで見たときの各パラメータの集合が j のときのコストの最小値

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;
const ll LINF = numeric_limits<ll>::max();

int main() {
  int n,k,p; cin >> n >> k >> p;
  vector<int> c(n);
  vector a(n,vector<int>(k));
  REP(i,n) {
    cin >> c[i];
    REP(j,k) cin >> a[i][j];
  }

  int base = p+1; // base 進数
  int sn = pow(base,k);
  // dp[i][j] := i 個までみたとき、パラメータ集合が j のときの最小コスト
  vector dp(n+1,vector<ll>(sn, LINF));
  dp[0][0] = 0;
  REP(i,n) REP(s,sn) {
    if (dp[i][s] == LINF) continue;
    // 選ばない
    dp[i+1][s] = min(dp[i+1][s],dp[i][s]);
    // 選ぶ
    int x = 1, nex = s;
    REP(j,k) {
      int now = s / x % base; // 現在のパラメータ値
      nex += (min(p, now + a[i][j]) - now) * x;
      x *= base;
    }
    dp[i+1][nex] = min(dp[i+1][nex],dp[i][s] + c[i]);
  }

  int x = 1, t = 0;
  REP(i,k) { t += p * x; x *= base; }
  if (dp[n][t] == LINF) dp[n][t] = -1;
  cout << dp[n][t] << endl;
  return 0;
}

雑感

D問題の実装重くて、ひやひやした。E問題もDPで解く方針はすぐに立てられたものの、パラメータの集合の持ち方が普段と違いすんなりいかなかった。もうちょっと早く解きたいところ。

tic40さんのAtCoder Beginner Contest 322での成績:1311位
パフォーマンス:1415相当
レーティング:1097→1133 (+36) :)
#AtCoder #ABC322 https://atcoder.jp/users/tic40/history/share/abc322?lang=ja 

AtCoder abc321 参加メモ

SuntoryProgrammingContest2023(AtCoder Beginner Contest 321) - AtCoder

B - Cutoff

N ラウンド目に取り得る値を全探索する

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  int n,x; cin >> n >> x;
  vector<int> a(n-1);
  REP(i,n-1) cin >> a[i];

  int ans = -1;
  REP(i,101) {
    auto b = a;
    b.push_back(i);
    sort(b.begin(),b.end());
    int tot = accumulate(b.begin()+1,b.end()-1,0);
    if (tot >= x) { ans = i; break; }
  }

  cout << ans << endl;
  return 0;
}

C - 321-like Searcher

x は最大で 9,876,543,210 になる。 同じ数は現れないため、0〜10 の数それぞれ1度だけ使うか、一度も使わないかどちらか。これは 210 と小さいため DFS で全列挙できる。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;

int main() {
  int k; cin >> k; k--;

  vector<ll> ans;
  auto dfs = [&](auto self, ll i) -> void {
    ans.push_back(i);
    REP(ni,i%10) self(self,i*10+ni);
  };

  for(ll i = 1; i <= 9; i++) dfs(dfs,i);
  sort(ans.begin(),ans.end());
  cout << ans[k] << endl;
  return 0;
}

D - Set Menu

まず a, b は sort して問題ない。 a を固定して考えたとき、a[i] + b[j] >= p となる j の位置は二分探索で求まる。 このときの

  • p 未満となる合計は a[i] * j + (b[0] + b[1] + ... b[j-1])
  • p 以上となる合計は p * (m-j)

b[0] + b[1] + ... + b[x] は累積和を求めておくと O(1) で求められる

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;

int main() {
  ll n,m,p; cin >> n >> m >> p;
  vector<ll> a(n),b(m);
  REP(i,n) cin >> a[i];
  REP(i,m) cin >> b[i];

  sort(a.begin(),a.end());
  sort(b.begin(),b.end());
  vector<ll> bs(m+1); // b の累積和
  REP(i,m) bs[i+1] = bs[i] + b[i];

  ll ans = 0;
  // a を固定して考える
  REP(i,n) {
    // a+b >= p となる b の index
    int idx = lower_bound(b.begin(),b.end(),p-a[i]) - b.begin();
    // a+b < p となる組み合わせの合計
    ans += a[i] * idx + bs[idx];
    // a+b >= p となる組み合わせの合計
    ans += (m-idx) * p;
  }
  cout << ans << endl;
  return 0;
}

雑感

MacOS アップデートしたら C++ コンパイル通らなくなるという... 開始5分前に気づいて焦った。結局ローカルでコンパイルできないままコンテスト始まってしまったため、AtCoder のコードテストで動作確認することにしてしのいだ。https://atcoder.jp/contests/apg4b/custom_test

E - Complete Binary Tree は時間が十分にあったものの、解ききれず。典型ぽくない adhoc な問題がでるとなかなか解けないな。

tic40さんのサントリープログラミングコンテスト2023(AtCoder Beginner Contest 321)での成績:1624位
パフォーマンス:1326相当
レーティング:1069→1097 (+28) :)
#AtCoder #サントリープログラミングコンテスト2023(ABC321) https://atcoder.jp/users/tic40/history/share/abc321?lang=ja 

AtCoder ARC 165 B - Sliding Window Sort 2

B - Sliding Window Sort 2

公式解説 を読んで AC したので紹介。

添字をバグらせ続けてなかなか苦戦したので、誰かの参考になれば。

Submission #45783935 - AtCoder Regular Contest 165

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  int n,k; cin >> n >> k;
  vector<int> p(n);
  REP(i,n) cin >> p[i];

  vector<int> s(n); // s[i] := p[i-1] < p[i] が成り立つ個数の累積和
  REP(i,n-1) s[i+1] = s[i] + (p[i+1] > p[i] ? 1 : 0);
  vector<int> sm(n,1e9); // sm[i] := p[n-k]...p[i] の累積 min
  for(int i = n-k; i < n; i++) sm[i] = min(sm[i-1], p[i]);

  // l-r 間をソートして答えを出力
  auto ans = [&](int l = 0, int r = 0) {
    sort(p.begin()+l,p.begin()+r);
    for(auto v: p) cout << v << " ";
    exit(0);
  };

  REP(l,n) {
    int r = l+k-1;
    if (r >= n) break;
    // p[l] < p[l+1] < p[l+2] < ... < p[r] が成り立つ
    if (s[r] - s[l] == k-1) ans();
  }

  REP(l,n) {
    int r = l+k-1;
    if (r >= n) break;
    if (r < n-k) continue;
    // 条件1: p[l] < p[l+1] < ... < p[n-k] が成り立つ
    bool ok = (s[n-k] - s[l]) == n-k-l;
    // 条件2: p[n-k], p[n-k+1],..., p[r] の最小値は p[n-k-1] より大きい
    ok = ok && p[n-k-1] < sm[r];
    if (ok) ans(l,r+1);
  }

  ans(n-k,n);
  return 0;
}

AtCoder abc320 参加メモ

Toyota Programming Contest 2023#5(AtCoder Beginner Contest 320) - AtCoder

B - Longest Palindrome

二重ループで全探索。回文は reverse した文字列と等しいかどうかで判定

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'

int main() {
  string s; cin >> s;
  int n = s.size();

  auto check = [&](string s) {
    auto t = s;
    reverse(t.begin(),t.end());
    return t == s;
  };

  int ans = 0;
  REP(i,n) for(int j = 1; j+i <= n; j++) {
    if (check(s.substr(i,j))) ans = max(ans,j);
  }
  cout << ans << endl;
  return 0;
}

C - Slot Strategy 2 (Easy)

  • リールが全て同じ数で止まるケースは 9 通りしかない(000,111,222,...,999)
  • 同じ時間に止められるリールは一つ。またリールはどれから止めても良いため、止める順番は 3! = 6 通りある

上記をうまく探索する。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
const int INF = numeric_limits<int>::max();

// リールを止める順番
const vector<vector<int>> orders = {
  { 0,1,2 },{ 0,2,1 },
  { 1,0,2 },{ 1,2,0 },
  { 2,0,1 },{ 2,1,0 }
};

int main() {
  int m; cin >> m;
  vector<string> s(3);
  REP(i,3) cin >> s[i];
  // g[i][j] := i 番目のリールでスロットの表示に j が現れる時間の集合  
  vector g(3,vector(10,vector<int>()));
  REP(i,3) REP(j,m) g[i][s[i][j]-'0'].push_back(j);

  auto impossible = [&](int i) {
    REP(j,3) if (g[j][i].size() == 0) return true;
    return false;
  };

  int ans = INF;
  REP(i,10) {
    if (impossible(i)) continue; // i|i|i でリールを止められるか
    for(auto order: orders) {
      int t = 0;
      for(int reel: order) {
        int modt = t%m;
        auto it = lower_bound(g[reel][i].begin(),g[reel][i].end(),modt);
        if (it == g[reel][i].end()) t += m - modt + g[reel][i][0];
        else t += *it - modt;
        t += 1;
      }
      ans = min(ans,t-1);
    }
  }

  cout << (ans == INF ? -1 : ans) << endl;
  return 0;
}

D - Relative Position

絶対座標がわかっているのは人 1 のみ。グラフとして考えて人 1 から到達可能な点は絶対座標がわかる。DFS で到達不可能な点は undecidable となる。

#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;
using P = pair<int,pair<ll,ll>>;

int main() {
  int n,m; cin >> n >> m;
  vector<int> a(m),b(m);
  vector<ll> x(m),y(m);
  vector g(n,vector<P>());

  REP(i,m) {
    cin >> a[i] >> b[i] >> x[i] >> y[i];
    a[i]--; b[i]--;
    g[a[i]].push_back({ b[i], {x[i],y[i]} });
    g[b[i]].push_back({ a[i], {-x[i],-y[i]} });
  }

  vector<pair<ll,ll>> ans(n);
  ans[0] = {0,0};
  vector<bool> visited(n);
  auto dfs = [&](auto self, int i) -> void {
    for(auto [v,pos]: g[i]) {
      if (visited[v]) continue;
      visited[v] = true;

      auto [nx,ny] = pos;
      auto [px,py] = ans[i];
      ans[v] = { px+nx, py+ny };
      self(self,v);
    }
  };
  visited[0] = true;
  dfs(dfs,0);

  REP(i,n) {
    if (!visited[i]) cout << "undecidable" << endl;
    else cout << ans[i].first << " " << ans[i].second << endl;
  }
  return 0;
}

E - Somen Nagashi

そうめん流しに並んでいる人、列から外れた人をそれぞれ2つの優先度付きキューに入れる。

  1. 出来事 i が起こったとき、列から外れた人のキューにその時間内に戻ってくる人がいたら、キューから出しそうめん流しに並んでいる人のキューへ加える。
  2. そうめん流しに並んでいる人のキューから番号の最も小さい人がそうめんを食べる。
  3. そうめんを食べた人を列から外れた人のキューへ追加する。
#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0;i<n;i++)
#define endl '\n'
using ll = long long;
using P = pair<ll,int>;

int main() {
  int n,m; cin >> n >> m;
  vector<ll> t(m),w(m),s(m);
  REP(i,m) cin >> t[i] >> w[i] >> s[i];

  // そうめん流しに並んでいる人
  priority_queue<int, vector<int>, greater<int>> pq1;
  // 列から外れた人
  priority_queue<P, vector<P>, greater<P>> pq2;
  REP(i,n) pq1.push(i);

  vector<ll> ans(n);
  REP(i,m) {
    // 列へ戻ってくる人の処理
    while(pq2.size()) {
      auto [vt,vi] = pq2.top();
      if (vt > t[i]) break;
      pq2.pop(); pq1.push(vi);
    }
    // そうめんを食べる人の処理
    if (pq1.empty()) continue;
    auto vi = pq1.top(); pq1.pop();
    ans[vi] += w[i];
    pq2.emplace(t[i]+s[i],vi);
  }

  for(auto v: ans) cout << v << endl;
  return 0;
}

雑感

直近 5 回のコンテストで酷い結果を連発しレートは highest から 150 も下がる始末。今日こそはなんとかしたかった。前回といい近頃の C 問題は難しいね。 今回は E まで解けたので 1000番台 に入ってるかと思いきや 2000 番台。 F 問題は動的計画法で解く方針を立てたものの時間が足りず。解くスピードが足りないな。