Codeforces Round #641 (Div. 1) A. Orac and LCM
gcdを求めたいので素因数ごとに独立に考えて、最後に掛け合わせても問題ない。
例えば数列が であったとする。このときのlcmの集合は
となる。このときの は となる。このように指数が小さい方から2番目の値のものが の指数となる。
あとは素因数ごとに指数が小さい方から2番目のものを列挙していく。指数が0のものもすべて列挙すると 素数の個数 かかってしまうので、各数の素因数のみを参照するように工夫する。
素因数を約数にもつ数の個数、素因数の指数の最小、素因数の指数の小さい方から2番目 とした配列を持つ。数 の素因数分解を行い、現れた素因数についてこれらの配列の更新を行う。これは でできる。
配列を構成した後、 であれば を答えに掛け、 であれば (最小の指数は0なので)を答えに掛ける。 のときは2番目から小さい方の指数は0なので何もしなくても問題ない。
のときに を掛けてシステス落とした…
ダブリング
この間のABCで話題になってたのでダブリングについてちょっと考えたことのメモ
コードは適当なので雰囲気です
を繰り返す
関数 が存在
について を 回実行
これは の計算量 で計算可能
繰り返し二乗法などが具体例としてあげられる
template<class T, class F> T binpow(T y, ll k, F f, T x) { T ret = x, p = y; while(k > 0) { if(k%2 == 0) {p = f(p, p); k /= 2;} else {ret = f(ret, p); k--;} } return ret; }; int main() { // 10^100 mod 1e9+7 binpow(10, 100, [](ll a, ll b){ return a*b%1000000007; }, 1); }
行列累乗や置換の積を繰り返すような処理も f, x
を変更することで対応可能
int main() { // 行列累乗 { const ll n = 3; // 行列の次数 using mind = modint<1000000007>; // modintの実装は省略します auto mul = [&](vector<vector<mint>> a, vector<vector<mint>> b) { vector<vector<mint>> c(n, vector<mint>(n)); REP(i, n) REP(j, n) REP(k, n) c[i][j] += a[i][k] * b[k][j]; return c; }; vector<vector<mint>> a = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, d = { // 単位行列 {1, 0, 0}, {0, 1, 0}, {0, 0, 1} }; // a^10 binpow(a, 10, mul, d); } // 置換の積 { const ll n = 3; // 置換の要素数 auto mul = [](vector<ll> a, vector<ll> b) { vector<ll> ret(a.size()); REP(i, a.size()) ret[i] = b[a[i]]; return ret; }; // (0, 2, 1) を 10回適用 binpow({0, 2, 1}, 10, mul, {0, 1, 2}); } }
このあいだのABCのD - Teleporterを置換の積と思うと、これで解ける
Submission #13117116 - AtCoder Beginner Contest 167
を繰り返す
に関数 を 回適用したときの値を計算する
と表す
を で計算可能なとき、前計算が で可能
各クエリ で計算可能
前計算
前計算では 番目の要素の個次の要素 を計算する
まず、 で初期化する。 に関しては と計算可能
クエリ
に関数 を 回適用したい
もし K&1<<i
であったら として更新する
実装例
template<ll LOG=60> struct doubling { // nxt[i][j] = (j番目の要素に関数fを2^i回適用した要素は何番目の要素か) vector<vector<ll>> nxt; doubling(vector<ll> nxt0) : nxt(LOG, vector<ll>(nxt0.size())) { REP(i, nxt0.size()) nxt[0][i] = nxt0[i]; FOR(k, 1, LOG) REP(j, nxt0.size()) nxt[k][j] = nxt[k-1][nxt[k-1][j]]; } // x番目の要素に関数fをk回適用した要素は何番目の要素か? ll query(ll x, ll k) { for(ll i=LOG-1; i>=0; --i) if(k&1LL<<i) x = nxt[i][x]; return x; } };
この間のABCのD - Teleporterの場合、 とすればよい
Submission #13117497 - AtCoder Beginner Contest 167
合計・最小などの求解
前計算のタイミングで 回適用したところまでの合計、最小を求めておけばよい
試しに合計で考える
に 回適用した値の合計
→ に 回 → に 回適用 に 回適用
より小さい の情報を用いて計算できるので、 が小さい方から計算していける
二分探索
単調性があるなら二分探索できる
みたいな単調性があるとする
から 以上に移動するのに必要な関数適用の回数の最小
が成り立つなら を が大きい方から実行すればよい
(実装例) Submission #13117640 - AtCoder Regular Contest 060
ダブリングで木のLCAを求めるのも同じ感じ
雑感
なんか一般化できないかなあと思って考えてたけど のやつ同じように扱うの難しそう
の方だと が大きくてもいける
の方だとクエリに高速に答えられる
第2回 ドワンゴからの挑戦状 予選 C - メンテナンス明け
経路を一つ固定したとき、寝過ごしたときに最悪となるパターンについて考える。辺 で寝過ごした場合、かかる時間は (始点からuまでの時間) + (uから終点までの時間) + (終点からtまでの時間) となる。経路上の各辺で寝過ごした場合の時間のmaxが最悪となるパターンである。この時間が最小となるような経路を求める問題である。
終点からdijkstra
頂点からまでで最悪な位置で寝た場合の最短時間 とする。辺 について 辺重み 辺重み 路線の終点までの時間 路線の終点からTまでの時間 となる。 からスタートしてdijkstra法のように最小のものから順に確定させていけばよい。
これが通常の最短距離を求めるdijkstra法と同様に最適解が求められることを確認する。最短経路問題では辺 を使用する場合、始点から までは最短距離で移動するのが最善である(負辺がないなら)。したがって、最短距離が確定した頂点から隣接した頂点に移動するパターンについて更新すればよい。
辺 で に移動するとき、 から までは最短の移動時間とするのが最善である。よって、dijkstra法の要領で最短が確定した頂点から順に確定させていくことで最適解を得ることができる。
自分の誤答
頂点からまで移動する場合に最悪な位置で寝たパターン, からの移動時間 というペアを持つことにする。辺 について 寝過ごした場合の分 辺重み のペア と更新する。
dijkstra法のように行っても、これでは最適解を求めることはできない。辺 を移動する場合、 が最小のものが最適とは限らない。…らしいんだけどどういうケースなのか思いつかなかったのでランダム生成した。20頂点10路線で始点が0で終点が19のケースになっている。
このケースで正しいのは0→7→11→19という経路で0→7で眠る場合の138となる
誤答の方は0→7→3→19という経路の149を出力する
0→7→11 とくる場合のdist[11]は(138,58)であるが、0→1→6→11 とくる場合のdist[11]は(136,117)でこちらの方が小さいため、0→7→11の方の経路は使用されない
ペアの辞書順で最小のものが常に正しいわけではないのでこのアルゴリズムはおかしい
dijkstra法を行うときはちゃんと条件を確認する必要がある
- 最短が確定した頂点から移動するような経路が必ず最適になるように実行
- ちゃんと全順序になっていること
ARC052 D - 9
と表す。 は下から 桁目の値であり、 である。 となるような を求めたい。
の組は 通り存在するため、普通に全列挙するとTLEしてしまう。そこで半分全列挙を行う。 と についてそれぞれ和を列挙する。足したときに で0になるようなものが何個あるのか求めればよい。
上限 の設定から を列挙するのが多少面倒になっているが、上位部分が と一致するか?の場合分けをすればよい。
半分全列挙見えなかった…
数字和と元の数字の一致、 進数のときに するくらいしか性質を知らない(なんかあるのかな?)ので、これでだめなら全探索ベースで考えるとかした方がいいのかも…?
Donutsプロコンチャレンジ2015 D - ドーナツの箱詰め
まず のときの解き方を考える。 についてソートしておく。この数列の差分について小さい方から 個の和が答えになる。差分が大きいところから 個と一番小さい箱にドーナツを入れることでこれは達成できる。
数列の小さい方から 個の和を求めるのにはセグ木、平衡二分探索木などのデータ構造を用いればよい。今回はRBSTを使う。
番目の箱が潰されるときのデータ構造の変化について考える。 番目の箱より小さい最大の箱を 、 番目の箱より大きい最小の箱を と表すことにする。、 をRBSTから消去し、 をRBSTに追加すればよい。prev, next については std::set で実現可能なのでこの問題は解けた。
天下一プログラマーコンテスト2014予選A D - EMLauncher
原点からの半直線を引くので関係があるのは始点と終点の偏角である。各線分を[始点の偏角、終点の偏角]の区間に置き換えると、「ある区間の集合が与えられる。任意の区間についていずれかの要素を含むような偏角の集合のうち、最小の要素のものを答えろ。」という問題に置き換えられる。
偏角は循環しているが、一旦単純化して循環していない単なる区間の列について考える。終点でソートしておく。このとき、一番最初の区間については終点を集合に追加するべきである。また、始点が一番最初の区間の終点より前にある区間についても条件を満たす。条件をまだ満たしていない区間のうち、一番最初の区間について同様の処理を行う。
このような循環する座標系においては始点を固定することで列に置き換えるのが常套手段で、この問題では始点となる区間を 個試すことで循環している条件に対応することができる。
線分を偏角の区間に置き換えるところについて、誤差の処理だったりが結構大変
偏角 かつ 始点の偏角終点の偏角 かつ 終点の偏角始点の偏角 となるようにしておく。こうしても一般性を失わないし、頭が壊れにくくなる。始点の偏角 から eps を引き、終点の偏角 に eps を足しておくといいっぽい。
#include <bits/stdc++.h> using namespace std; using ll = long long; using PII = pair<ll, ll>; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() template<typename T> void chmin(T &a, const T &b) { a = min(a, b); } template<typename T> void chmax(T &a, const T &b) { a = max(a, b); } struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio; #ifdef DEBUG #include "../program_contest_library/memo/dump.hpp" #else #define dump(...) #endif const ll INF = 1LL<<60; using R = long double; const R EPS = 1e-8; const R PI = atan(1)*4; inline int sgn(const R &r) { return (r>EPS) - (r<-EPS); } inline int sgn(const R &r1, const R &r2) { return sgn(r1-r2); } int main(void) { ll n; cin >> n; vector<ll> sx(n), sy(n), gx(n), gy(n); REP(i, n) cin >> sx[i] >> sy[i] >> gx[i] >> gy[i]; vector<pair<R,R>> v0(n); REP(i, n) { R sarg = atan2l(sy[i], sx[i]); R garg = atan2l(gy[i], gx[i]); if(sarg > garg) swap(sarg, garg); if(garg-sarg > PI) swap(sarg, garg); sarg -= EPS; garg += EPS; if(sarg < 0) sarg += 2*PI; while(garg < sarg) garg += 2*PI; v0[i] = {sarg, garg}; } sort(ALL(v0)); ll ans = n; REP(i, n) { // 区間iの終点に対応する半直線を引く // この半直線に交差しない区間を列挙する R t = v0[i].second; vector<pair<R,R>> v; REP(j, n) { bool cross1 = sgn(v0[j].first, t) <= 0 && sgn(t, v0[j].second) <= 0; bool cross2 = sgn(v0[j].first, t+2*PI) <= 0 && sgn(t+2*PI, v0[j].second) <= 0; if(cross1 || cross2) continue; if(sgn(t, v0[j].first) < 0) v.emplace_back(v0[j].first, v0[j].second); if(sgn(v0[j].second, t) < 0) v.emplace_back(v0[j].first+2*PI, v0[j].second+2*PI); } // 終点でソートして貪欲にとっていく sort(ALL(v), [](PII l, PII r){ return PII(l.second, l.first) < PII(r.second, r.first); }); ll ret = 1, x = 0; for(; x<(ll)v.size();) { ll hold = x; for(; x<(ll)v.size() && sgn(v[x].first, v[hold].second) <= 0; ++x); ret++; } chmin(ans, ret); } cout << ans << endl; return 0; }
チーム練習 (05/07)
Dashboard - 2019-2020 ICPC Southwestern European Regional Programming Contest (SWERC 2019-20) - Codeforces
8完ペナ1044で本番9位相当
A
dp[頂点][距離] = コストでDP
dijkstraしなくていいのでlog落とせる
B,C
気づいたら通されてたので知らない
D
struct state {
ll val;
state* left, right;
};
deque<state> dq;
みたいなのをもってがんばる。そのままやるとTLEするのでハッシュ化する。
E
マス を操作するなら を1、操作しないなら0にする
各マスを 'B','W' のどちらにするかで式が立てられる
mod 2 で に隣接する の和が0か1になるような式
普通にgauss jordanするとTLEだが、行列が疎とか形が特殊とかでできるらしい
解けません
F
多角形の面積を求める
G
相対順序が入れ替わらないペアが存在
仲がよくないペア について、 個目の より前に が何個あるか?を前計算
置ける中で小さいものから貪欲に置いていく
H
漸化式について となるような が存在する
start, periodについて前計算をして埋め込みを行うことで で計算可能
前計算を行う際に 個のメモリをもつのは厳しいが、フロイドの循環検出法などを使うと space でいける
#include <bits/stdc++.h> using namespace std; using ll = long long; using PII = pair<ll, ll>; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() template<typename T> void chmin(T &a, const T &b) { a = min(a, b); } template<typename T> void chmax(T &a, const T &b) { a = max(a, b); } struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio; #ifdef DEBUG #include "../program_contest_library/memo/dump.hpp" #else #define dump(...) #endif const ll INF = 1LL<<60; const ll start = 350125310, period = 182129209, len = 500000; // 長すぎるので省略 pre_calcで計算したものを埋め込み ll v0[], v1[], x0[], x1[]; // 初項s0でs_n=next(s_{n-1})となる数列 // s_i = s_{i+period} (i>=start) となるようなstart, periodを探索 // time: O(start+period) space: O(1) template<class F> PII floyd_cycle_find(const ll s0, F next) { ll t = 0, h = 1, st = s0, sh = next(s0); for(; st!=sh; t++, st=next(st), h+=2, sh=next(next(sh))); ll start = 0; sh = s0; REP(i, h-t) sh = next(sh); for(st=s0; st!=sh; ++start, st=next(st), sh=next(sh)); ll period = 1; for(sh=next(st); st!=sh; ++period, sh=next(sh)); return make_pair(start, period); } /* 線形合同法などに使われるらしい const ll s0 = 1611516670; auto next = [](ll s) { constexpr ll m = 1LL<<40; return (s + (s>>20) + 12345) & (m-1); }; auto [start, period] = floyd_cycle_find(s0, next); */ void pre_calc() { const ll s0 = 1611516670; auto next = [](ll s) { constexpr ll m = 1LL<<40; return (s + (s>>20) + 12345) & (m-1); }; auto [start, period] = floyd_cycle_find(s0, next); ll s = s0; // O(len) ごとにまとめておくと、各クエリ O(len) かかるけど埋め込む長さを減らせる constexpr ll len = 500000; const ll start1 = (start+len-1)/len, period1 = (period+len-1)/len; vector<ll> v0(start1), v1(period1), x0(start1), x1(period1); REP(i, start) { if(i%len == 0) x0[i/len] = s; (v0[i/len] += (s%2==0)); s = next(s); } FOR(i, 1, start1) (v0[i] += v0[i-1]); REP(i, period) { if(i%len == 0) x1[i/len] = s; (v1[i/len] += (s%2==0)); s = next(s); } FOR(i, 1, period1) (v1[i] += v1[i-1]); cout << "ll v0[] = {"; REP(i, start1) cout << v0[i] << (i==start1-1 ? "};\nll x0[] = {" : ","); REP(i, start1) cout << x0[i] << (i==start1-1 ? "};\nll v1[] = {" : ","); REP(i, period1) cout << v1[i] << (i==period1-1 ? "};\nll x1[] = {" : ","); REP(i, period1) cout << x1[i] << (i==period1-1 ? "};\n" : ","); } int main(void) { // pre_calc(); ll n; cin >> n; n--; if(n < 0) { cout << 0 << endl; return 0; } auto next = [](ll s) { constexpr ll m = 1LL<<40; return (s + (s>>20) + 12345) & (m-1); }; ll ans = 0; if(n < start) { if(n/len >= 1) ans += v0[n/len-1]; ll s = x0[n/len]; REP(i, n%len+1) { (ans += (s%2==0)); s = next(s); } } else { // [0, start-1] ans += v0[(start+len-1)/len - 1]; n -= start; // [start, start+period*k-1] (ans += n / period * v1[(period+len-1)/len - 1]); n %= period; // [start, start+n] if(n/len >= 1) (ans += v1[n/len-1]); ll s = x1[n/len]; REP(i, n%len+1) { (ans += (s%2==0)); s = next(s); } } cout << ans << endl; return 0; }
I
気づいたら通ってた
J
最小の数字のみからできる二分木をつくり、残りの数字をその二分木の子孫で表現すると再帰的につくっていける
k頂点の二分木の数はカタラン数でわかる
K
Tに隣接 && Tに行く方法が1通りの頂点の列挙
辺の向きを逆にしたグラフでTから何通りかを求めたい
SCCしてDAGにしてDPする
L
の2次元グリッド
firm ground, wet zone, protected zone の3タイプのマス
連結なwet zoneからなる集合をwet areaと呼ぶ
任意のwet areaについて以下の性質がある
- グリッド上の左(右)から一番目のマスにwet zoneが存在
- 要素数 以下
- 異なるwet areaのマスは距離3以上離れている
2人でゲームをする
以下の制約にしたがってマスにカメラを置く
置けなくなったら負け
- firm groundであること
- wet zoneに隣接
- protected zoneでないこと
- 同じマスにはカメラは1個まで
- 同じwet areaに属するカメラは隣接してはいけない
同じwet zoneに隣接している && 連結 であるようなfirm groundの集合を考える
異なる集合同士については独立に解くことが可能
集合ごとにgrundy数を求めたい
集合の要素数は高々 なので でdfsしてgrundy数を求めればよい
#include <bits/stdc++.h> using namespace std; using ll = long long; using PII = pair<ll, ll>; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() template<typename T> void chmin(T &a, const T &b) { a = min(a, b); } template<typename T> void chmax(T &a, const T &b) { a = max(a, b); } struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio; #ifdef DEBUG #include "../program_contest_library/memo/dump.hpp" #else #define dump(...) #endif const ll INF = 1LL<<60; struct UnionFind { vector<ll> par, s; UnionFind(ll n) : par(n), s(n, 1) { iota(ALL(par), 0); } ll find(ll x) { if(par[x] == x) return x; return par[x] = find(par[x]); } void unite(ll x, ll y) { x = find(x), y = find(y); if(x == y) return; if(s[x] < s[y]) par[x] = y, s[y] += s[x]; else par[y] = x, s[x] += s[y]; } bool same(int x, int y) { return find(x) == find(y); } ll size(int x) { return s[find(x)]; } }; int main(void) { ll n; cin >> n; vector<string> s(n); REP(i, n) cin >> s[i]; ll dx[] = {0, -1, 0, 1}, dy[] = {-1, 0, 1, 0}; vector<vector<ll>> t(n, vector<ll>(n, -1)); { auto dfs = [&](auto &&self, ll y, ll x, ll type) -> void { t[y][x] = type; REP(i, 4) { ll ny = y + dy[i], nx = x + dx[i]; if(nx<0 || nx>=n || ny<0 || ny>=n) continue; if(s[ny][nx]=='*' && t[ny][nx]==-1) self(self, ny, nx, type); } }; ll type = 0; REP(i, n) REP(j, n) { if(t[i][j] == -1 && s[i][j]=='*') { dfs(dfs, i, j, type); type++; } } } vector<pair<PII,ll>> p; REP(i, n) REP(j, n) { if(s[i][j] != '.') continue; REP(k, 4) { ll ny = i + dy[k], nx = j + dx[k]; if(nx<0 || nx>=n || ny<0 || ny>=n) continue; if(s[ny][nx] == '*') { p.push_back({{i, j}, t[ny][nx]}); break; } } } UnionFind uf(p.size()); FOR(i, 1, p.size()) { if(p[i-1].second == p[i].second && p[i-1].first.first==p[i].first.first && abs(p[i-1].first.second-p[i].first.second)<=1) { uf.unite(i-1, i); } } vector<ll> ord(p.size()); iota(ALL(ord), 0); sort(ALL(ord), [&](ll l, ll r){ return PII(p[l].first.second, p[l].first.first) < PII(p[r].first.second, p[r].first.first); }); FOR(i0, 1, ord.size()) { ll i = ord[i0], pre = ord[i0-1]; if(p[pre].second==p[i].second && p[pre].first.second==p[i].first.second && abs(p[pre].first.first-p[i].first.first)<=1) { uf.unite(pre, i); } } vector<vector<PII>> v(p.size()); REP(i, p.size()) v[uf.find(i)].push_back(p[i].first); auto solve = [&](vector<PII> a) { const ll m = a.size(); vector<ll> memo((1LL<<m), -1); vector<vector<ll>> b(m); auto dfs = [&](auto &&self, ll mask) -> ll { if(memo[mask]!=-1) return memo[mask]; vector<bool> grundy(m+1); REP(i, m) { bool flag = true; for(auto j: b[i]) if(mask&1LL<<j) { flag = false; break; } if(flag) { ll ret = self(self, mask | 1LL<<i); grundy[ret] = true; } } REP(i, m+1) if(!grundy[i]) { return memo[mask] = i; } assert(false); }; REP(i, m) { b[i].push_back(i); REP(j, m) { if(i==j) continue; if(a[i].first==a[j].first && abs(a[i].second-a[j].second)<=1) b[i].push_back(j); if(a[i].second==a[j].second && abs(a[i].first-a[j].first)<=1) b[i].push_back(j); } } ll ret = dfs(dfs, 0); return ret; }; ll g = 0; for(auto i: v) if(i.size()) { ll ret = solve(i); g ^= ret; dump(i, ret); } if(g == 0) cout << "Second"; else cout << "First"; cout << " player will win\n"; return 0; }