AtCoder Beginner Contest 177 感想
Eでやらかした1WA、無事に世界一くやしい1WAになりました。
— scol (@scol_kp) 2020年8月29日
scolさんのAtCoder Beginner Contest 177での成績:599位
パフォーマンス:1736相当
レーティング:1575→1592 (+17) :)
Highestを更新しました!#AtCoder #ABC177 https://t.co/ofoLvNjGbF
もう髪の毛の先くらいは青に染まってると思うねんけど。
A - Don't be late
入力例3の高橋君、やる気がなさすぎる。下の「間に合いません。」でウケた。
リンク: https://atcoder.jp/contests/abc177/submissions/16304998提出コード
void solve() {
ll D, T, S;
cin >> D >> T >> S;
prints()(D <= T * S ? "Yes" : "No");
}
B - Substring
最初、「連続」を読み飛ばしていて、「ん?むずくね?」となった。
リンク: https://atcoder.jp/contests/abc177/submissions/16312925提出コード
void solve() {
string S, T;
cin >> S >> T;
ll ans = L_INF;
for (ll i = 0; i < (ll)S.size(); i++) {
if (i + (ll)T.size() > (ll)S.size()) break;
ll cnt = 0;
for (ll j = 0; j < (ll)T.size(); j++) {
if (S[i + j] != T[j]) {
++cnt;
}
}
chmin(ans, cnt);
}
prints()(ans);
}
C - Sum of product of pairs
愚直にやるのは なのできびしい。
は
と表せるので、累積和を前計算しておけば解を高速に求められる。
計算量は前計算が 、解の計算が 。
この手の総和の順番を入れ替えて高速化する問題は度々出てるとはいえ、灰diffなのはびっくりした。茶色くらいはあると思った。
リンク: https://atcoder.jp/contests/abc177/submissions/16317845提出コード
// ModInt 略
constexpr ll MOD = 1e9 + 7;
using Mint = ModInt<MOD>;
void solve() {
ll N;
cin >> N;
vector<Mint> A(N);
for (ll i = 0; i < N; i++) {
cin >> A[i];
}
vector<Mint> cum(N + 1);
for (ll i = 0; i < N; i++) {
cum[i + 1] = cum[i] + A[i];
}
Mint ans = 0;
for (ll i = 0; i < N; i++) {
ans += A[i] * (cum[N] - cum[i + 1]);
}
prints()(ans);
}
D - Friends
Union-Find を持っていたら秒で終わる問題。
Union-FInd で友達関係を素集合として管理する。素集合の最大要素数を とすると
- 明らかに 未満のグループ数では目的は達成できない(鳩ノ巣原理)。
- グループ数が 以上ならば、目的を達成できるグループの分け方が必ず存在する。
なので、目的を達成できる最小のグループ数は 。
リンク: https://atcoder.jp/contests/abc177/submissions/16322341提出コード
class UnionFind {
using i64 = std::int64_t;
private:
std::vector<i64> par;
std::vector<i64> sz;
public:
UnionFind(i64 N) : par(N), sz(N, 1) {
for (i64 i = 0; i < N; ++i) par[i] = i;
}
i64 root(i64 x) {
return par[x] == x ? x : par[x] = root(par[x]);
}
bool same(i64 x, i64 y) {
return root(x) == root(y);
}
void unite(i64 x, i64 y) {
x = root(x);
y = root(y);
if (x == y) return;
if (sz[x] < sz[y]) std::swap(x, y);
par[y] = x;
sz[x] += sz[y];
}
i64 size(i64 x) {
return sz[root(x)];
}
void reset(i64 N) {
par.resize(N);
for (i64 i = 0; i < N; ++i) par[i] = i;
sz.assign(N, 1);
}
};
void solve() {
ll N, M;
cin >> N >> M;
UnionFind uf(N);
for (ll i = 0; i < M; i++) {
int a, b;
cin >> a >> b;
--a, --b;
uf.unite(a, b);
}
ll ans = 0;
for (ll i = 0; i < N; i++) {
chmax(ans, uf.size(i));
}
prints()(ans);
}
E - Coprime
を次のように定義する。
このとき、
- が pairwise coprime ⇔
- が setwise coprime ⇔
が言えるので、これを使って判定する。
は を素因数分解すると求められるけど、試し割り法で愚直にやるのは かかるのでちょっと嫌な気分になる(でもなんかこれでも通るらしい?)。 実はもっと高速に素因数分解できる方法がある。ちょっと前にそれを Twitter で見かけられたのは幸運だった。
の最小の素因数を と書くことにする。また、 とおく。 はエラトステネスの篩の要領で で求められて、 これを使うと、 を満たす整数 を で素因数分解できる。 この方法で、各 を素因数分解すればいい。
の構築に 、 個の素因数分解クエリに かかるので、の素因数分解にかかる計算量は全部で 。
の構築を でできる実装もある → https://cp-algorithms.com/algebra/prime-sieve-linear.html
まぁ なんてほとんど気にならない差なのでエラトステネスの篩でいいかな。
リンク: https://atcoder.jp/contests/abc177/submissions/16336620提出コード
void solve() {
constexpr int MX_V = 1000000;
int cp[MX_V + 1];
Vi ps; // <- これいらん(笑)焦りすぎでしょ。
for (ll i = 0; i <= MX_V; i++) {
cp[i] = -1;
}
for (ll i = 2; i <= MX_V; i++) {
if (cp[i] == -1) {
ps.emplace_back(i);
cp[i] = i;
}
for (ll j = i + i; j <= MX_V; j += i) {
if (cp[j] != -1) continue;
cp[j] = i;
}
}
ll N;
cin >> N;
Vl A(N);
for (ll i = 0; i < N; i++) {
cin >> A[i];
}
int ns[MX_V + 1];
for (ll i = 0; i <= MX_V; i++) {
ns[i] = 0;
}
set<int> st;
for (ll i = 0; i < N; i++) {
st.clear();
int t = A[i];
while (t > 1) {
ll q = cp[t];
if (q > 1) {
st.emplace(q);
}
t /= q;
}
for (auto x : st) {
++ns[x];
}
}
ll mx = 0;
for (ll i = 0; i < MX_V; i++) {
chmax(mx, ns[i]);
}
if (mx <= 1) {
prints()("pairwise coprime");
}
else if (mx < N) {
prints()("setwise coprime");
}
else {
prints()("not coprime");
}
}
F - I hate Shortest Path Problem
解けなかった。でも、コンテスト中にかなり惜しいところまで考察できてた。くやしい~~~。以下、コンテスト後の考察。
in-place DP で解ける。
行 列目のマスを と書くことにする。
を次のように定義する。
ただし、 は「マス に到達するために必要な横への移動回数の最小値 (到達できない場合は )」。
番目の出力は とすればいい。
区間min・区間更新を処理できる遅延セグ木2本を使うと から が で求められる。
から を求めるのも でできる。
配列の初期化なども併せて計算量は全部で 。
リンク: https://atcoder.jp/contests/abc177/submissions/16405505コンテスト後提出コード
template <class T, class E, class F, class G, class H, class P>
class LazySegmentTree {
private:
const F f;
const G g;
const H h;
const P p;
const T tid;
const E eid;
std::vector<T> vec;
std::vector<E> lazy;
int sz;
public:
explicit LazySegmentTree(
int n, F f, G g, H h, P p, T tid, E eid) noexcept
: f(f), g(g), h(h), p(p), tid(tid), eid(eid) {
sz = 1;
while (sz < n) sz <<= 1;
vec.assign(sz * 2 - 1, tid);
lazy.assign(sz * 2 - 1, eid);
}
T& operator[](int idx) noexcept {
return vec[sz - 1 + idx];
}
void set_value(int idx, T val) noexcept {
vec[sz - 1 + idx] = val;
}
void build() noexcept {
for (int i = sz - 2; i >= 0; i--) {
vec[i] = f(vec[i * 2 + 1], vec[i * 2 + 2]);
}
}
void eval(int k, int l, int r) noexcept {
if (lazy[k] == eid) return;
if (r - l > 1) {
lazy[k * 2 + 1] = h(lazy[k * 2 + 1], lazy[k]);
lazy[k * 2 + 2] = h(lazy[k * 2 + 2], lazy[k]);
}
vec[k] = g(vec[k], p(lazy[k], r - l));
lazy[k] = eid;
}
void update(int a, int b, E x) noexcept {
_update(a, b, x, 0, 0, sz);
}
T query(int a, int b) noexcept {
return _query(a, b, 0, 0, sz);
}
private:
T _update(int a, int b, E x, int k, int l, int r) noexcept {
eval(k, l, r);
if (b <= l || r <= a) return vec[k];
if (a <= l && r <= b) {
lazy[k] = h(lazy[k], x);
return g(vec[k], p(lazy[k], r - l));
}
else {
T t1 = _update(a, b, x, k * 2 + 1, l, (l + r) >> 1);
T t2 = _update(a, b, x, k * 2 + 2, (l + r) >> 1, r);
return vec[k] = f(t1, t2);
}
}
T _query(int a, int b, int k, int l, int r) noexcept {
if (b <= l || r <= a) return tid;
eval(k, l, r);
if (a <= l && r <= b) {
return vec[k];
}
else {
T t1 = _query(a, b, k * 2 + 1, l, (l + r) >> 1);
T t2 = _query(a, b, k * 2 + 2, (l + r) >> 1, r);
return f(t1, t2);
}
}
};
void solve() {
ll H, W;
cin >> H >> W;
vector<Pii> walls(H);
for (ll i = 0; i < H; i++) {
int a, b;
cin >> a >> b;
--a, --b;
walls[i] = {a, b};
}
ll tid = L_INF;
ll eid = -1;
auto opf = [](ll a, ll b) -> ll { return min(a, b); };
auto opg = [&](ll a, ll b) -> ll { return b == eid ? a : b; };
auto oph = [&](ll a, ll b) -> ll { return b == eid ? a : b; };
auto opp = [](ll a, ll k) -> ll { return a; };
LazySegmentTree seg1(W, opf, opg, oph, opp, tid, eid);
LazySegmentTree seg2(W, opf, opg, oph, opp, tid, eid);
for (ll i = 0; i < W; i++) {
seg1[i] = 0;
seg2[i] = W - 1 - i;
}
seg1.build();
seg2.build();
for (ll i = 0; i < H; i++) {
auto [left, right] = walls[i];
if (right + 1 < W) {
ll q = seg2.query(left, right + 2);
if (q != tid) {
q -= W - 1 - (right + 1);
seg1.update(right + 1, right + 2, q);
seg2.update(right + 1, right + 2, q + W - 1 - (right + 1));
}
}
seg1.update(left, right + 1, tid);
seg2.update(left, right + 1, tid);
ll res = seg1.query(0, W);
if (res == tid) {
prints()(-1);
}
else {
prints()(res + i + 1);
}
// for (ll i = 0; i < W; i++) {
// prints("", " ")(seg1.query(i, i + 1));
// }
// prints()();
// for (ll i = 0; i < W; i++) {
// prints("", " ")(seg2.query(i, i + 1));
// }
// prints()();
}
}